initial commit
This commit is contained in:
commit
21556e171e
140 changed files with 28972 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/data
|
||||||
|
/work
|
||||||
|
/logs
|
||||||
|
/.idea
|
||||||
|
/target
|
||||||
|
.DS_Store
|
||||||
|
*.iml
|
||||||
|
/.settings
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.gradle
|
||||||
|
build
|
||||||
|
out
|
191
LICENSE.txt
Normal file
191
LICENSE.txt
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, and
|
||||||
|
distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||||
|
owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||||
|
that control, are controlled by, or are under common control with that entity.
|
||||||
|
For the purposes of this definition, "control" means (i) the power, direct or
|
||||||
|
indirect, to cause the direction or management of such entity, whether by
|
||||||
|
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||||
|
permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, including
|
||||||
|
but not limited to software source code, documentation source, and configuration
|
||||||
|
files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical transformation or
|
||||||
|
translation of a Source form, including but not limited to compiled object code,
|
||||||
|
generated documentation, and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||||
|
available under the License, as indicated by a copyright notice that is included
|
||||||
|
in or attached to the work (an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||||
|
is based on (or derived from) the Work and for which the editorial revisions,
|
||||||
|
annotations, elaborations, or other modifications represent, as a whole, an
|
||||||
|
original work of authorship. For the purposes of this License, Derivative Works
|
||||||
|
shall not include works that remain separable from, or merely link (or bind by
|
||||||
|
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including the original version
|
||||||
|
of the Work and any modifications or additions to that Work or Derivative Works
|
||||||
|
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||||
|
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||||
|
on behalf of the copyright owner. For the purposes of this definition,
|
||||||
|
"submitted" means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems, and
|
||||||
|
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||||
|
the purpose of discussing and improving the Work, but excluding communication
|
||||||
|
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||||
|
owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||||
|
of whom a Contribution has been received by Licensor and subsequently
|
||||||
|
incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||||
|
Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor hereby
|
||||||
|
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||||
|
irrevocable (except as stated in this section) patent license to make, have
|
||||||
|
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||||
|
such license applies only to those patent claims licensable by such Contributor
|
||||||
|
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||||
|
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||||
|
Contribution incorporated within the Work constitutes direct or contributory
|
||||||
|
patent infringement, then any patent licenses granted to You under this License
|
||||||
|
for that Work shall terminate as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||||
|
in any medium, with or without modifications, and in Source or Object form,
|
||||||
|
provided that You meet the following conditions:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||||
|
all copyright, patent, trademark, and attribution notices from the Source form
|
||||||
|
of the Work, excluding those notices that do not pertain to any part of the
|
||||||
|
Derivative Works; and
|
||||||
|
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||||
|
Derivative Works that You distribute must include a readable copy of the
|
||||||
|
attribution notices contained within such NOTICE file, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||||
|
following places: within a NOTICE text file distributed as part of the
|
||||||
|
Derivative Works; within the Source form or documentation, if provided along
|
||||||
|
with the Derivative Works; or, within a display generated by the Derivative
|
||||||
|
Works, if and wherever such third-party notices normally appear. The contents of
|
||||||
|
the NOTICE file are for informational purposes only and do not modify the
|
||||||
|
License. You may add Your own attribution notices within Derivative Works that
|
||||||
|
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||||
|
provided that such additional attribution notices cannot be construed as
|
||||||
|
modifying the License.
|
||||||
|
You may add Your own copyright statement to Your modifications and may provide
|
||||||
|
additional or different license terms and conditions for use, reproduction, or
|
||||||
|
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||||
|
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||||
|
with the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||||
|
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||||
|
conditions of this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||||
|
any separate license agreement you may have executed with Licensor regarding
|
||||||
|
such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names, trademarks,
|
||||||
|
service marks, or product names of the Licensor, except as required for
|
||||||
|
reasonable and customary use in describing the origin of the Work and
|
||||||
|
reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||||
|
Work (and each Contributor provides its Contributions) 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. You are
|
||||||
|
solely responsible for determining the appropriateness of using or
|
||||||
|
redistributing the Work and assume any risks associated with Your exercise of
|
||||||
|
permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including negligence),
|
||||||
|
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||||
|
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special, incidental,
|
||||||
|
or consequential damages of any character arising as a result of this License or
|
||||||
|
out of the use or inability to use the Work (including but not limited to
|
||||||
|
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such Contributor has
|
||||||
|
been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||||
|
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||||
|
other liability obligations and/or rights consistent with this License. However,
|
||||||
|
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||||
|
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||||
|
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason of your
|
||||||
|
accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following boilerplate
|
||||||
|
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||||
|
identifying information. (Don't include the brackets!) The text should be
|
||||||
|
enclosed in the appropriate comment syntax for the file format. We also
|
||||||
|
recommend that a file or class name and description of purpose be included on
|
||||||
|
the same "printed page" as the copyright notice for easier identification within
|
||||||
|
third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed 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.
|
7
NOTICE.txt
Normal file
7
NOTICE.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
This work is based upon
|
||||||
|
|
||||||
|
https://github.com/susom/database
|
||||||
|
|
||||||
|
as of 28 Dec 2021
|
||||||
|
|
||||||
|
License: Apache 2.0
|
32
build.gradle
Normal file
32
build.gradle
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
plugins {
|
||||||
|
id "de.marcphilipp.nexus-publish" version "0.4.0"
|
||||||
|
id "io.codearte.nexus-staging" version "0.21.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
gradleVersion = "${project.property('gradle.wrapper.version')}"
|
||||||
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
user = 'jprante'
|
||||||
|
name = 'database'
|
||||||
|
description = 'JDBC connection pool and utilities'
|
||||||
|
inceptionYear = '2018'
|
||||||
|
url = 'https://github.com/' + user + '/' + name
|
||||||
|
scmUrl = 'https://github.com/' + user + '/' + name
|
||||||
|
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
|
||||||
|
scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git'
|
||||||
|
issueManagementSystem = 'Github'
|
||||||
|
issueManagementUrl = ext.scmUrl + '/issues'
|
||||||
|
licenseName = 'The Apache License, Version 2.0'
|
||||||
|
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply from: rootProject.file('gradle/ide/idea.gradle')
|
||||||
|
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||||
|
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||||
|
apply from: rootProject.file('gradle/publishing/publication.gradle')
|
||||||
|
}
|
11
gradle.properties
Normal file
11
gradle.properties
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
group = org.xbib
|
||||||
|
name = database
|
||||||
|
version = 0.0.1
|
||||||
|
|
||||||
|
org.gradle.warning.mode = ALL
|
||||||
|
gradle.wrapper.version = 7.3.2
|
||||||
|
h2.version = 1.4.200
|
||||||
|
mockito.version = 3.3.3
|
||||||
|
testcontainers.version = 1.16.2
|
||||||
|
derby.version = 10.15.2.0
|
||||||
|
oracle-client.version = 21.4.0.0
|
43
gradle/compile/java.gradle
Normal file
43
gradle/compile/java.gradle
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
java {
|
||||||
|
modularity.inferModulePath.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestJava {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes('Implementation-Version': project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||||
|
classifier 'sources'
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
}
|
||||||
|
|
||||||
|
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||||
|
classifier 'javadoc'
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
archives sourcesJar, javadocJar
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs << '-Xlint:all,-fallthrough'
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
}
|
55
gradle/documentation/asciidoc.gradle
Normal file
55
gradle/documentation/asciidoc.gradle
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
asciidoclet
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
asciidoctor {
|
||||||
|
backends 'html5'
|
||||||
|
outputDir = file("${rootProject.projectDir}/docs")
|
||||||
|
separateOutputDirs = false
|
||||||
|
attributes 'source-highlighter': 'coderay',
|
||||||
|
idprefix: '',
|
||||||
|
idseparator: '-',
|
||||||
|
toc: 'left',
|
||||||
|
doctype: 'book',
|
||||||
|
icons: 'font',
|
||||||
|
encoding: 'utf-8',
|
||||||
|
sectlink: true,
|
||||||
|
sectanchors: true,
|
||||||
|
linkattrs: true,
|
||||||
|
imagesdir: 'img',
|
||||||
|
stylesheet: "${projectDir}/src/docs/asciidoc/css/foundation.css"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*javadoc {
|
||||||
|
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||||
|
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||||
|
//options.overview = "src/docs/asciidoclet/overview.adoc"
|
||||||
|
options.addStringOption "-base-dir", "${projectDir}"
|
||||||
|
options.addStringOption "-attribute",
|
||||||
|
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||||
|
configure(options) {
|
||||||
|
noTimestamp = true
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
/*javadoc {
|
||||||
|
options.docletpath = configurations.asciidoclet.files.asType(List)
|
||||||
|
options.doclet = 'org.asciidoctor.Asciidoclet'
|
||||||
|
options.overview = "${rootProject.projectDir}/src/docs/asciidoclet/overview.adoc"
|
||||||
|
options.addStringOption "-base-dir", "${projectDir}"
|
||||||
|
options.addStringOption "-attribute",
|
||||||
|
"name=${project.name},version=${project.version},title-link=https://github.com/xbib/${project.name}"
|
||||||
|
options.destinationDirectory(file("${projectDir}/docs/javadoc"))
|
||||||
|
configure(options) {
|
||||||
|
noTimestamp = true
|
||||||
|
}
|
||||||
|
}*/
|
13
gradle/ide/idea.gradle
Normal file
13
gradle/ide/idea.gradle
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
outputDir file('build/classes/java/main')
|
||||||
|
testOutputDir file('build/classes/java/test')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.convention.findPlugin(JavaPluginConvention)) {
|
||||||
|
//sourceSets.main.output.classesDirs = file("build/classes/java/main")
|
||||||
|
//sourceSets.test.output.classesDirs = file("build/classes/java/test")
|
||||||
|
}
|
0
gradle/publish.gradle
Normal file
0
gradle/publish.gradle
Normal file
64
gradle/publishing/publication.gradle
Normal file
64
gradle/publishing/publication.gradle
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
|
||||||
|
apply plugin: "de.marcphilipp.nexus-publish"
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
artifact sourcesJar
|
||||||
|
artifact javadocJar
|
||||||
|
pom {
|
||||||
|
name = project.name
|
||||||
|
description = rootProject.ext.description
|
||||||
|
url = rootProject.ext.url
|
||||||
|
inceptionYear = rootProject.ext.inceptionYear
|
||||||
|
packaging = 'jar'
|
||||||
|
organization {
|
||||||
|
name = 'xbib'
|
||||||
|
url = 'https://xbib.org'
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = 'jprante'
|
||||||
|
name = 'Jörg Prante'
|
||||||
|
email = 'joergprante@gmail.com'
|
||||||
|
url = 'https://github.com/jprante'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
url = rootProject.ext.scmUrl
|
||||||
|
connection = rootProject.ext.scmConnection
|
||||||
|
developerConnection = rootProject.ext.scmDeveloperConnection
|
||||||
|
}
|
||||||
|
issueManagement {
|
||||||
|
system = rootProject.ext.issueManagementSystem
|
||||||
|
url = rootProject.ext.issueManagementUrl
|
||||||
|
}
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = rootProject.ext.licenseName
|
||||||
|
url = rootProject.ext.licenseUrl
|
||||||
|
distribution = 'repo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("signing.keyId")) {
|
||||||
|
apply plugin: 'signing'
|
||||||
|
signing {
|
||||||
|
sign publishing.publications.mavenJava
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nexusPublishing {
|
||||||
|
repositories {
|
||||||
|
sonatype {
|
||||||
|
username = project.property('ossrhUsername')
|
||||||
|
password = project.property('ossrhPassword')
|
||||||
|
packageGroup = "org.xbib"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
gradle/publishing/sonatype.gradle
Normal file
11
gradle/publishing/sonatype.gradle
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||||
|
|
||||||
|
apply plugin: 'io.codearte.nexus-staging'
|
||||||
|
|
||||||
|
nexusStaging {
|
||||||
|
username = project.property('ossrhUsername')
|
||||||
|
password = project.property('ossrhPassword')
|
||||||
|
packageGroup = "org.xbib"
|
||||||
|
}
|
||||||
|
}
|
27
gradle/test/junit5.gradle
Normal file
27
gradle/test/junit5.gradle
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.6.2'
|
||||||
|
def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}"
|
||||||
|
testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
failFast = false
|
||||||
|
testLogging {
|
||||||
|
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||||
|
}
|
||||||
|
afterSuite { desc, result ->
|
||||||
|
if (!desc.parent) {
|
||||||
|
println "\nTest result: ${result.resultType}"
|
||||||
|
println "Test summary: ${result.testCount} tests, " +
|
||||||
|
"${result.successfulTestCount} succeeded, " +
|
||||||
|
"${result.failedTestCount} failed, " +
|
||||||
|
"${result.skippedTestCount} skipped"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
234
gradlew
vendored
Executable file
234
gradlew
vendored
Executable file
|
@ -0,0 +1,234 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed 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
|
||||||
|
#
|
||||||
|
# https://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.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
26
jdbc-connection-pool/NOTICE.txt
Normal file
26
jdbc-connection-pool/NOTICE.txt
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
This connection pool implementation is a derived work from HikariCP Version 3.4.5 (May 2020)
|
||||||
|
|
||||||
|
https://github.com/brettwooldridge/HikariCP
|
||||||
|
|
||||||
|
published under Apache 2.0 License.
|
||||||
|
|
||||||
|
Motivations for the derived work:
|
||||||
|
|
||||||
|
- remove all dependencies
|
||||||
|
- remove everything with metrics
|
||||||
|
- remove everything with JMX
|
||||||
|
- remove everything with slf4j logging
|
||||||
|
- remove everything with OSGI, Hibernate, Spring, JNDI
|
||||||
|
- remove everything with javassist
|
||||||
|
- remove everything with suspend/resume
|
||||||
|
- fix module-info.java
|
||||||
|
- remove MacOS "milli second" clock
|
||||||
|
- clean up source code, packages, inheritances, inner classes refactoring, get rid of helper classes
|
||||||
|
- no system property dark magic
|
||||||
|
- no addDataSourceProperty magic, pass a Properties object always to PoolConfig which contains JDBC driver properties
|
||||||
|
- Java 11+
|
||||||
|
- JUnit 5+
|
||||||
|
- Gradle 6.4+
|
||||||
|
|
||||||
|
The result is an 88k jar.
|
4
jdbc-connection-pool/build.gradle
Normal file
4
jdbc-connection-pool/build.gradle
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dependencies {
|
||||||
|
testImplementation "com.h2database:h2:${project.property('h2.version')}"
|
||||||
|
testImplementation "org.mockito:mockito-core:${project.property('mockito.version')}"
|
||||||
|
}
|
6
jdbc-connection-pool/src/main/java/module-info.java
Normal file
6
jdbc-connection-pool/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module org.xbib.jdbc.connection.pool {
|
||||||
|
requires java.logging;
|
||||||
|
requires transitive java.sql;
|
||||||
|
exports org.xbib.jdbc.connection.pool;
|
||||||
|
exports org.xbib.jdbc.connection.pool.util;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
public enum IsolationLevel {
|
||||||
|
TRANSACTION_NONE(0),
|
||||||
|
TRANSACTION_READ_UNCOMMITTED(1),
|
||||||
|
TRANSACTION_READ_COMMITTED(2),
|
||||||
|
TRANSACTION_REPEATABLE_READ(4),
|
||||||
|
TRANSACTION_SERIALIZABLE(8),
|
||||||
|
TRANSACTION_SQL_SERVER_SNAPSHOT_ISOLATION_LEVEL(4096);
|
||||||
|
|
||||||
|
private final int levelId;
|
||||||
|
|
||||||
|
IsolationLevel(int levelId) {
|
||||||
|
this.levelId = levelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLevelId() {
|
||||||
|
return levelId;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,666 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public class PoolConfig {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PoolConfig.class.getName());
|
||||||
|
|
||||||
|
private static final AtomicLong POOL_COUNTER = new AtomicLong();
|
||||||
|
|
||||||
|
private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
|
||||||
|
|
||||||
|
private static final long VALIDATION_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
|
||||||
|
|
||||||
|
private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
|
||||||
|
|
||||||
|
private static final long MAX_LIFETIME = TimeUnit.MINUTES.toMillis(30);
|
||||||
|
|
||||||
|
private static final int DEFAULT_POOL_SIZE = 8;
|
||||||
|
|
||||||
|
private final Properties properties;
|
||||||
|
|
||||||
|
private volatile long connectionTimeout;
|
||||||
|
|
||||||
|
private volatile long validationTimeout;
|
||||||
|
|
||||||
|
private volatile long idleTimeout;
|
||||||
|
|
||||||
|
private volatile long leakDetectionThreshold;
|
||||||
|
|
||||||
|
private volatile long maxLifetime;
|
||||||
|
|
||||||
|
private volatile int maxPoolSize;
|
||||||
|
|
||||||
|
private volatile int minIdle;
|
||||||
|
|
||||||
|
private volatile String username;
|
||||||
|
|
||||||
|
private volatile String password;
|
||||||
|
|
||||||
|
private long initializationFailTimeout;
|
||||||
|
|
||||||
|
private String connectionInitSql;
|
||||||
|
|
||||||
|
private String connectionTestQuery;
|
||||||
|
|
||||||
|
private String dataSourceClassName;
|
||||||
|
|
||||||
|
private String driverClassName;
|
||||||
|
|
||||||
|
private String jdbcUrl;
|
||||||
|
|
||||||
|
private String poolName;
|
||||||
|
|
||||||
|
private String catalog;
|
||||||
|
|
||||||
|
private String schema;
|
||||||
|
|
||||||
|
private String transactionIsolationName;
|
||||||
|
|
||||||
|
private boolean isAutoCommit;
|
||||||
|
|
||||||
|
private boolean isReadOnly;
|
||||||
|
|
||||||
|
private boolean isIsolateInternalQueries;
|
||||||
|
|
||||||
|
private boolean isAllowPoolSuspension;
|
||||||
|
|
||||||
|
private long aliveBypassWindowMs;
|
||||||
|
|
||||||
|
private long housekeepingPeriodMs;
|
||||||
|
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
private ThreadFactory threadFactory;
|
||||||
|
|
||||||
|
private ScheduledExecutorService scheduledExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public PoolConfig() {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link PoolConfig} from the specified properties object.
|
||||||
|
*
|
||||||
|
* @param properties the name of the property file
|
||||||
|
*/
|
||||||
|
public PoolConfig(Properties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
this.minIdle = -1;
|
||||||
|
this.maxPoolSize = -1;
|
||||||
|
this.maxLifetime = MAX_LIFETIME;
|
||||||
|
this.connectionTimeout = CONNECTION_TIMEOUT;
|
||||||
|
this.validationTimeout = VALIDATION_TIMEOUT;
|
||||||
|
this.idleTimeout = IDLE_TIMEOUT;
|
||||||
|
this.initializationFailTimeout = -1;
|
||||||
|
this.isAutoCommit = true;
|
||||||
|
this.jdbcUrl = properties.getProperty("url");
|
||||||
|
this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500);
|
||||||
|
this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCatalog() {
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCatalog(String catalog) {
|
||||||
|
this.catalog = catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getConnectionTimeout() {
|
||||||
|
return connectionTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionTimeout(long connectionTimeoutMs) {
|
||||||
|
if (connectionTimeoutMs == 0) {
|
||||||
|
this.connectionTimeout = Integer.MAX_VALUE;
|
||||||
|
} else if (connectionTimeoutMs < 250) {
|
||||||
|
throw new IllegalArgumentException("connectionTimeout cannot be less than 250ms");
|
||||||
|
} else {
|
||||||
|
this.connectionTimeout = connectionTimeoutMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getIdleTimeout() {
|
||||||
|
return idleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdleTimeout(long idleTimeoutMs) {
|
||||||
|
if (idleTimeoutMs < 0) {
|
||||||
|
throw new IllegalArgumentException("idleTimeout cannot be negative");
|
||||||
|
}
|
||||||
|
this.idleTimeout = idleTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLeakDetectionThreshold() {
|
||||||
|
return leakDetectionThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeakDetectionThreshold(long leakDetectionThresholdMs) {
|
||||||
|
this.leakDetectionThreshold = leakDetectionThresholdMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMaxLifetime() {
|
||||||
|
return maxLifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLifetime(long maxLifetimeMs) {
|
||||||
|
this.maxLifetime = maxLifetimeMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximumPoolSize() {
|
||||||
|
return maxPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximumPoolSize(int maxPoolSize) {
|
||||||
|
if (maxPoolSize < 1) {
|
||||||
|
throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
|
||||||
|
}
|
||||||
|
this.maxPoolSize = maxPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinimumIdle() {
|
||||||
|
return minIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimumIdle(int minIdle) {
|
||||||
|
if (minIdle < 0) {
|
||||||
|
throw new IllegalArgumentException("minimumIdle cannot be negative");
|
||||||
|
}
|
||||||
|
this.minIdle = minIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default password to use for DataSource.getConnection(username, password) calls.
|
||||||
|
*
|
||||||
|
* @return the password
|
||||||
|
*/
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default password to use for DataSource.getConnection(username, password) calls.
|
||||||
|
*
|
||||||
|
* @param password the password
|
||||||
|
*/
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default username used for DataSource.getConnection(username, password) calls.
|
||||||
|
*
|
||||||
|
* @return the username
|
||||||
|
*/
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default username used for DataSource.getConnection(username, password) calls.
|
||||||
|
*
|
||||||
|
* @param username the username
|
||||||
|
*/
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getValidationTimeout() {
|
||||||
|
return validationTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidationTimeout(long validationTimeoutMs) {
|
||||||
|
if (validationTimeoutMs < 250) {
|
||||||
|
throw new IllegalArgumentException("validationTimeout cannot be less than 250ms");
|
||||||
|
}
|
||||||
|
this.validationTimeout = validationTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SQL query to be executed to test the validity of connections.
|
||||||
|
*
|
||||||
|
* @return the SQL query string, or null
|
||||||
|
*/
|
||||||
|
public String getConnectionTestQuery() {
|
||||||
|
return connectionTestQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the SQL query to be executed to test the validity of connections. Using
|
||||||
|
* the JDBC4 <code>Connection.isValid()</code> method to test connection validity can
|
||||||
|
* be more efficient on some databases and is recommended.
|
||||||
|
*
|
||||||
|
* @param connectionTestQuery a SQL query string
|
||||||
|
*/
|
||||||
|
public void setConnectionTestQuery(String connectionTestQuery) {
|
||||||
|
this.connectionTestQuery = connectionTestQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SQL string that will be executed on all new connections when they are
|
||||||
|
* created, before they are added to the pool.
|
||||||
|
*
|
||||||
|
* @return the SQL to execute on new connections, or null
|
||||||
|
*/
|
||||||
|
public String getConnectionInitSql() {
|
||||||
|
return connectionInitSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the SQL string that will be executed on all new connections when they are
|
||||||
|
* created, before they are added to the pool. If this query fails, it will be
|
||||||
|
* treated as a failed connection attempt.
|
||||||
|
*
|
||||||
|
* @param connectionInitSql the SQL to execute on new connections
|
||||||
|
*/
|
||||||
|
public void setConnectionInitSql(String connectionInitSql) {
|
||||||
|
this.connectionInitSql = connectionInitSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link DataSource} that has been explicitly specified to be wrapped by the
|
||||||
|
* pool.
|
||||||
|
*
|
||||||
|
* @return the {@link DataSource} instance, or null
|
||||||
|
*/
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a {@link DataSource} for the pool to explicitly wrap. This setter is not
|
||||||
|
* available through property file based initialization.
|
||||||
|
*
|
||||||
|
* @param dataSource a specific {@link DataSource} to be wrapped by the pool
|
||||||
|
*/
|
||||||
|
public void setDataSource(DataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the JDBC {@link DataSource} class used to create Connections.
|
||||||
|
*
|
||||||
|
* @return the fully qualified name of the JDBC {@link DataSource} class
|
||||||
|
*/
|
||||||
|
public String getDataSourceClassName() {
|
||||||
|
return dataSourceClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the fully qualified class name of the JDBC {@link DataSource} that will be used create Connections.
|
||||||
|
*
|
||||||
|
* @param className the fully qualified name of the JDBC {@link DataSource} class
|
||||||
|
*/
|
||||||
|
public void setDataSourceClassName(String className) {
|
||||||
|
this.dataSourceClassName = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getProperties() {
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDriverClassName() {
|
||||||
|
return driverClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDriverClassName(String driverClassName) {
|
||||||
|
Class<?> driverClass = attemptFromContextLoader(driverClassName);
|
||||||
|
try {
|
||||||
|
if (driverClass == null) {
|
||||||
|
driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
|
||||||
|
logger.log(Level.FINE, () -> "driver class found in the PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.log(Level.SEVERE, "failed to load driver class from PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
if (driverClass == null) {
|
||||||
|
throw new RuntimeException("failed to load driver class " + driverClassName + " in either of PoolConfig class loader or Thread context classloader");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
driverClass.getConstructor().newInstance();
|
||||||
|
this.driverClassName = driverClassName;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to instantiate class " + driverClassName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default auto-commit behavior of connections in the pool.
|
||||||
|
*
|
||||||
|
* @return the default auto-commit behavior of connections
|
||||||
|
*/
|
||||||
|
public boolean isAutoCommit() {
|
||||||
|
return isAutoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default auto-commit behavior of connections in the pool.
|
||||||
|
*
|
||||||
|
* @param isAutoCommit the desired auto-commit default for connections
|
||||||
|
*/
|
||||||
|
public void setAutoCommit(boolean isAutoCommit) {
|
||||||
|
this.isAutoCommit = isAutoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pool suspension behavior (allowed or disallowed).
|
||||||
|
*
|
||||||
|
* @return the pool suspension behavior
|
||||||
|
*/
|
||||||
|
public boolean isAllowPoolSuspension() {
|
||||||
|
return isAllowPoolSuspension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not pool suspension is allowed. There is a performance
|
||||||
|
* impact when pool suspension is enabled. Unless you need it (for a
|
||||||
|
* redundancy system for example) do not enable it.
|
||||||
|
*
|
||||||
|
* @param isAllowPoolSuspension the desired pool suspension allowance
|
||||||
|
*/
|
||||||
|
public void setAllowPoolSuspension(boolean isAllowPoolSuspension) {
|
||||||
|
this.isAllowPoolSuspension = isAllowPoolSuspension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the pool initialization failure timeout. See {@code #setInitializationFailTimeout(long)}
|
||||||
|
* for details.
|
||||||
|
*
|
||||||
|
* @return the number of milliseconds before the pool initialization fails
|
||||||
|
* @see PoolConfig#setInitializationFailTimeout(long)
|
||||||
|
*/
|
||||||
|
public long getInitializationFailTimeout() {
|
||||||
|
return initializationFailTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the pool initialization failure timeout. This setting applies to pool
|
||||||
|
* initialization when {@link PoolDataSource} is constructed with a {@link PoolConfig},
|
||||||
|
* or when {@link PoolDataSource} is constructed using the no-arg constructor
|
||||||
|
* and {@link PoolDataSource#getConnection()} is called.
|
||||||
|
* <ul>
|
||||||
|
* <li>Any value greater than zero will be treated as a timeout for pool initialization.
|
||||||
|
* The calling thread will be blocked from continuing until a successful connection
|
||||||
|
* to the database, or until the timeout is reached. If the timeout is reached, then
|
||||||
|
* a {@code PoolInitializationException} will be thrown. </li>
|
||||||
|
* <li>A value of zero will <i>not</i> prevent the pool from starting in the
|
||||||
|
* case that a connection cannot be obtained. However, upon start the pool will
|
||||||
|
* attempt to obtain a connection and validate that the {@code connectionTestQuery}
|
||||||
|
* and {@code connectionInitSql} are valid. If those validations fail, an exception
|
||||||
|
* will be thrown. If a connection cannot be obtained, the validation is skipped
|
||||||
|
* and the the pool will start and continue to try to obtain connections in the
|
||||||
|
* background. This can mean that callers to {@code DataSource#getConnection()} may
|
||||||
|
* encounter exceptions. </li>
|
||||||
|
* <li>A value less than zero will bypass any connection attempt and validation during
|
||||||
|
* startup, and therefore the pool will start immediately. The pool will continue to
|
||||||
|
* try to obtain connections in the background. This can mean that callers to
|
||||||
|
* {@code DataSource#getConnection()} may encounter exceptions. </li>
|
||||||
|
* </ul>
|
||||||
|
* Note that if this timeout value is greater than or equal to zero (0), and therefore an
|
||||||
|
* initial connection validation is performed, this timeout does not override the
|
||||||
|
* {@code connectionTimeout} or {@code validationTimeout}; they will be honored before this
|
||||||
|
* timeout is applied. The default value is one millisecond.
|
||||||
|
*
|
||||||
|
* @param initializationFailTimeout the number of milliseconds before the
|
||||||
|
* pool initialization fails, or 0 to validate connection setup but continue with
|
||||||
|
* pool start, or less than zero to skip all initialization checks and start the
|
||||||
|
* pool without delay.
|
||||||
|
*/
|
||||||
|
public void setInitializationFailTimeout(long initializationFailTimeout) {
|
||||||
|
this.initializationFailTimeout = initializationFailTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
|
||||||
|
* via {@link Connection#rollback()}. Defaults to {@code false}.
|
||||||
|
*
|
||||||
|
* @return {@code true} if internal pool queries are isolated, {@code false} if not
|
||||||
|
*/
|
||||||
|
public boolean isIsolateInternalQueries() {
|
||||||
|
return isIsolateInternalQueries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure whether internal pool queries, principally aliveness checks, will be isolated in their own transaction
|
||||||
|
* via {@link Connection#rollback()}. Defaults to {@code false}.
|
||||||
|
*
|
||||||
|
* @param isolate {@code true} if internal pool queries should be isolated, {@code false} if not
|
||||||
|
*/
|
||||||
|
public void setIsolateInternalQueries(boolean isolate) {
|
||||||
|
this.isIsolateInternalQueries = isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the Connections in the pool are in read-only mode.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the Connections in the pool are read-only, {@code false} if not
|
||||||
|
*/
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the Connections to be added to the pool as read-only Connections.
|
||||||
|
*
|
||||||
|
* @param readOnly {@code true} if the Connections in the pool are read-only, {@code false} if not
|
||||||
|
*/
|
||||||
|
public void setReadOnly(boolean readOnly) {
|
||||||
|
this.isReadOnly = readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPoolName() {
|
||||||
|
return poolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of the connection pool. This is primarily used for the MBean
|
||||||
|
* to uniquely identify the pool configuration.
|
||||||
|
*
|
||||||
|
* @param poolName the name of the connection pool to use
|
||||||
|
*/
|
||||||
|
public void setPoolName(String poolName) {
|
||||||
|
this.poolName = poolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ScheduledExecutorService used for housekeeping.
|
||||||
|
*
|
||||||
|
* @return the executor
|
||||||
|
*/
|
||||||
|
public ScheduledExecutorService getScheduledExecutor() {
|
||||||
|
return scheduledExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ScheduledExecutorService used for housekeeping.
|
||||||
|
*
|
||||||
|
* @param executor the ScheduledExecutorService
|
||||||
|
*/
|
||||||
|
public void setScheduledExecutor(ScheduledExecutorService executor) {
|
||||||
|
this.scheduledExecutor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTransactionIsolation() {
|
||||||
|
return transactionIsolationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default schema name to be set on connections.
|
||||||
|
*
|
||||||
|
* @return the default schema name
|
||||||
|
*/
|
||||||
|
public String getSchema() {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default schema name to be set on connections.
|
||||||
|
*
|
||||||
|
* @param schema the name of the default schema
|
||||||
|
*/
|
||||||
|
public void setSchema(String schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default transaction isolation level. The specified value is the
|
||||||
|
* constant name from the <code>Connection</code> class, eg.
|
||||||
|
* <code>TRANSACTION_REPEATABLE_READ</code>.
|
||||||
|
*
|
||||||
|
* @param isolationLevel the name of the isolation level
|
||||||
|
*/
|
||||||
|
public void setTransactionIsolation(String isolationLevel) {
|
||||||
|
this.transactionIsolationName = isolationLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAliveBypassWindowMs(long aliveBypassWindowMs) {
|
||||||
|
this.aliveBypassWindowMs = aliveBypassWindowMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAliveBypassWindowMs() {
|
||||||
|
return aliveBypassWindowMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHousekeepingPeriodMs(long housekeepingPeriodMs) {
|
||||||
|
this.housekeepingPeriodMs = housekeepingPeriodMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHousekeepingPeriodMs() {
|
||||||
|
return housekeepingPeriodMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the thread factory used to create threads.
|
||||||
|
*
|
||||||
|
* @return the thread factory (may be null, in which case the default thread factory is used)
|
||||||
|
*/
|
||||||
|
public ThreadFactory getThreadFactory() {
|
||||||
|
return threadFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the thread factory to be used to create threads.
|
||||||
|
*
|
||||||
|
* @param threadFactory the thread factory (setting to null causes the default thread factory to be used)
|
||||||
|
*/
|
||||||
|
public void setThreadFactory(ThreadFactory threadFactory) {
|
||||||
|
this.threadFactory = threadFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Class<?> attemptFromContextLoader(final String driverClassName) {
|
||||||
|
final ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
if (threadContextClassLoader != null) {
|
||||||
|
try {
|
||||||
|
final Class<?> driverClass = threadContextClassLoader.loadClass(driverClassName);
|
||||||
|
logger.log(Level.FINE, "Driver class found in Thread context class loader:" +
|
||||||
|
driverClassName + " " + threadContextClassLoader);
|
||||||
|
return driverClass;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.log(Level.FINE, "Driver class not found in Thread context class loader, trying classloader: " +
|
||||||
|
driverClassName + " " + threadContextClassLoader + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
public void validate() {
|
||||||
|
if (poolName == null) {
|
||||||
|
poolName = generatePoolName();
|
||||||
|
}
|
||||||
|
catalog = getNullIfEmpty(catalog);
|
||||||
|
connectionInitSql = getNullIfEmpty(connectionInitSql);
|
||||||
|
connectionTestQuery = getNullIfEmpty(connectionTestQuery);
|
||||||
|
transactionIsolationName = getNullIfEmpty(transactionIsolationName);
|
||||||
|
dataSourceClassName = getNullIfEmpty(dataSourceClassName);
|
||||||
|
driverClassName = getNullIfEmpty(driverClassName);
|
||||||
|
jdbcUrl = getNullIfEmpty(jdbcUrl);
|
||||||
|
if (dataSource != null) {
|
||||||
|
if (dataSourceClassName != null) {
|
||||||
|
logger.log(Level.WARNING, "using dataSource and ignoring dataSourceClassName: " + poolName);
|
||||||
|
}
|
||||||
|
} else if (dataSourceClassName != null) {
|
||||||
|
if (driverClassName != null) {
|
||||||
|
logger.log(Level.SEVERE, "cannot use driverClassName and dataSourceClassName together: " + poolName);
|
||||||
|
throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together.");
|
||||||
|
} else if (jdbcUrl != null) {
|
||||||
|
logger.log(Level.WARNING, "using dataSourceClassName and ignoring jdbcUrl: " + poolName);
|
||||||
|
}
|
||||||
|
} else if (jdbcUrl != null) {
|
||||||
|
// ok
|
||||||
|
} else if (driverClassName != null) {
|
||||||
|
logger.log(Level.SEVERE, "jdbcUrl is required with driverClassName: " + poolName);
|
||||||
|
throw new IllegalArgumentException("jdbcUrl is required with driverClassName.");
|
||||||
|
} else {
|
||||||
|
logger.log(Level.SEVERE, "dataSource or dataSourceClassName or jdbcUrl is required: " + poolName);
|
||||||
|
throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required.");
|
||||||
|
}
|
||||||
|
validateNumerics();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null if string is null or empty
|
||||||
|
*/
|
||||||
|
private static String getNullIfEmpty(final String text) {
|
||||||
|
return text == null ? null : text.trim().isEmpty() ? null : text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateNumerics() {
|
||||||
|
if (maxLifetime != 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) {
|
||||||
|
logger.log(Level.WARNING, "maxLifetime is less than 30000ms, setting to default ms: " +
|
||||||
|
poolName + " " + MAX_LIFETIME);
|
||||||
|
maxLifetime = MAX_LIFETIME;
|
||||||
|
}
|
||||||
|
if (leakDetectionThreshold > 0) {
|
||||||
|
if (leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2) || (leakDetectionThreshold > maxLifetime && maxLifetime > 0)) {
|
||||||
|
logger.log(Level.WARNING, "leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it: " +
|
||||||
|
poolName);
|
||||||
|
leakDetectionThreshold = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connectionTimeout < 250) {
|
||||||
|
logger.log(Level.WARNING, "connectionTimeout is less than 250ms, setting to ms: " +
|
||||||
|
poolName + " " + CONNECTION_TIMEOUT);
|
||||||
|
connectionTimeout = CONNECTION_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (validationTimeout < 250) {
|
||||||
|
logger.log(Level.WARNING, "validationTimeout is less than 250ms, setting to ms" +
|
||||||
|
poolName + " " + VALIDATION_TIMEOUT);
|
||||||
|
validationTimeout = VALIDATION_TIMEOUT;
|
||||||
|
}
|
||||||
|
if (maxPoolSize < 1) {
|
||||||
|
maxPoolSize = DEFAULT_POOL_SIZE;
|
||||||
|
}
|
||||||
|
if (minIdle < 0 || minIdle > maxPoolSize) {
|
||||||
|
minIdle = maxPoolSize;
|
||||||
|
}
|
||||||
|
if (idleTimeout + TimeUnit.SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0 && minIdle < maxPoolSize) {
|
||||||
|
logger.log(Level.WARNING, "idleTimeout is close to or more than maxLifetime, disabling it:" + poolName);
|
||||||
|
idleTimeout = 0;
|
||||||
|
} else if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10) && minIdle < maxPoolSize) {
|
||||||
|
logger.log(Level.WARNING, "idleTimeout is less than 10000ms, setting to default ms: " +
|
||||||
|
poolName + " " + IDLE_TIMEOUT);
|
||||||
|
idleTimeout = IDLE_TIMEOUT;
|
||||||
|
} else if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) {
|
||||||
|
logger.log(Level.WARNING, "idleTimeout has been set but has no effect because the pool is operating as a fixed size pool: " + poolName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generatePoolName() {
|
||||||
|
return "xbib-pool-jdbc-" + POOL_COUNTER.getAndIncrement();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pooled DataSource.
|
||||||
|
*/
|
||||||
|
public class PoolDataSource implements DataSource, Closeable {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PoolDataSource.class.getName());
|
||||||
|
|
||||||
|
private final AtomicBoolean isShutdown = new AtomicBoolean();
|
||||||
|
|
||||||
|
private final PoolConfig configuration;
|
||||||
|
|
||||||
|
private Pool pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a {@link PoolDataSource} with the specified configuration. The
|
||||||
|
* {@link PoolConfig} is copied and the pool is started by invoking this
|
||||||
|
* constructor.
|
||||||
|
* The {@link PoolConfig} can be modified without affecting the DataSource
|
||||||
|
* and used to initialize another DataSource instance.
|
||||||
|
*
|
||||||
|
* @param configuration a config instance
|
||||||
|
*/
|
||||||
|
public PoolDataSource(PoolConfig configuration)
|
||||||
|
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||||
|
this.configuration = configuration;
|
||||||
|
pool = new Pool(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pool getPool() {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
if (isClosed()) {
|
||||||
|
throw new SQLException("PoolDataSource " + this + " has been closed");
|
||||||
|
}
|
||||||
|
Pool result = pool;
|
||||||
|
if (result == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
result = pool;
|
||||||
|
if (result == null) {
|
||||||
|
configuration.validate();
|
||||||
|
logger.log(Level.INFO, "Starting: " + configuration.getPoolName());
|
||||||
|
try {
|
||||||
|
pool = result = new Pool(configuration);
|
||||||
|
} catch (Exception pie) {
|
||||||
|
throw new SQLException(pie);
|
||||||
|
}
|
||||||
|
logger.log(Level.INFO, "Start completed: " + configuration.getPoolName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection(String username, String password) throws SQLException {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
|
Pool p = pool;
|
||||||
|
return (p != null ? p.getUnwrappedDataSource().getLogWriter() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
Pool p = pool;
|
||||||
|
if (p != null) {
|
||||||
|
p.getUnwrappedDataSource().setLogWriter(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
Pool p = pool;
|
||||||
|
if (p != null) {
|
||||||
|
p.getUnwrappedDataSource().setLoginTimeout(seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getLoginTimeout() throws SQLException {
|
||||||
|
Pool p = pool;
|
||||||
|
return (p != null ? p.getUnwrappedDataSource().getLoginTimeout() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(this)) {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
Pool p = pool;
|
||||||
|
if (p != null) {
|
||||||
|
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
|
||||||
|
if (iface.isInstance(unwrappedDataSource)) {
|
||||||
|
return (T) unwrappedDataSource;
|
||||||
|
}
|
||||||
|
if (unwrappedDataSource != null) {
|
||||||
|
return unwrappedDataSource.unwrap(iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new SQLException("wrapped DataSource is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(this)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Pool p = pool;
|
||||||
|
if (p != null) {
|
||||||
|
final DataSource unwrappedDataSource = p.getUnwrappedDataSource();
|
||||||
|
if (iface.isInstance(unwrappedDataSource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unwrappedDataSource != null) {
|
||||||
|
return unwrappedDataSource.isWrapperFor(iface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evict a connection from the pool. If the connection has already been closed (returned to the pool)
|
||||||
|
* this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
|
||||||
|
* currently in use. If the connection has not been closed, the eviction is immediate.
|
||||||
|
*
|
||||||
|
* @param connection the connection to evict from the pool
|
||||||
|
*/
|
||||||
|
public void evictConnection(Connection connection) {
|
||||||
|
Pool p;
|
||||||
|
if (!isClosed() && (p = pool) != null) {
|
||||||
|
p.evictConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the DataSource and its associated pool.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (isShutdown.getAndSet(true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pool p = pool;
|
||||||
|
if (p != null) {
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, () -> "shutdown initiated: " + configuration.getPoolName());
|
||||||
|
p.shutdown();
|
||||||
|
logger.log(Level.INFO, () -> "shutdown completed: " + configuration.getPoolName());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.log(Level.WARNING, "interrupted during closing: " + configuration.getPoolName() + " " + e.getMessage());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the DataSource has been closed.
|
||||||
|
*
|
||||||
|
* @return true if the DataSource has been closed, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isClosed() {
|
||||||
|
return isShutdown.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PoolDataSource (" + pool + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.FastList;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.BagEntry;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool entry used in the Bag to track Connection instances.
|
||||||
|
*/
|
||||||
|
public class PoolEntry implements BagEntry {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PoolEntry.class.getName());
|
||||||
|
|
||||||
|
private static final AtomicIntegerFieldUpdater<PoolEntry> stateUpdater =
|
||||||
|
AtomicIntegerFieldUpdater.newUpdater(PoolEntry.class, "state");
|
||||||
|
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
private long lastAccessed;
|
||||||
|
|
||||||
|
private long lastBorrowed;
|
||||||
|
|
||||||
|
private volatile int state = 0;
|
||||||
|
|
||||||
|
private volatile boolean evict;
|
||||||
|
|
||||||
|
private volatile ScheduledFuture<?> endOfLife;
|
||||||
|
|
||||||
|
private final FastList<Statement> openStatements;
|
||||||
|
|
||||||
|
private final Pool pool;
|
||||||
|
|
||||||
|
private final boolean isReadOnly;
|
||||||
|
|
||||||
|
private final boolean isAutoCommit;
|
||||||
|
|
||||||
|
public PoolEntry(Connection connection, Pool pool, final boolean isReadOnly, final boolean isAutoCommit) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.pool = pool;
|
||||||
|
this.isReadOnly = isReadOnly;
|
||||||
|
this.isAutoCommit = isAutoCommit;
|
||||||
|
this.lastAccessed = ClockSource.currentTime();
|
||||||
|
this.openStatements = new FastList<>(Statement.class, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConnection() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pool getPool() {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastAccessed() {
|
||||||
|
return lastAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastBorrowed() {
|
||||||
|
return lastBorrowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release this entry back to the pool.
|
||||||
|
*
|
||||||
|
* @param lastAccessed last access time-stamp
|
||||||
|
*/
|
||||||
|
public void recycle(final long lastAccessed) {
|
||||||
|
if (connection != null) {
|
||||||
|
this.lastAccessed = lastAccessed;
|
||||||
|
pool.recycle(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the end of life {@link ScheduledFuture}.
|
||||||
|
*
|
||||||
|
* @param endOfLife this PoolEntry/Connection's end of life {@link ScheduledFuture}
|
||||||
|
*/
|
||||||
|
public void setFutureEol(final ScheduledFuture<?> endOfLife) {
|
||||||
|
this.endOfLife = endOfLife;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection createProxyConnection(final ProxyLeakTask leakTask, final long now) {
|
||||||
|
return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPoolName() {
|
||||||
|
return pool.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMarkedEvicted() {
|
||||||
|
return evict;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markEvicted() {
|
||||||
|
this.evict = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evict(final String closureReason) {
|
||||||
|
pool.closeConnection(this, closureReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns millis since lastBorrowed
|
||||||
|
*/
|
||||||
|
public long getMillisSinceBorrowed() {
|
||||||
|
return ClockSource.elapsedMillis(lastBorrowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final long now = ClockSource.currentTime();
|
||||||
|
return connection
|
||||||
|
+ ", accessed " + ClockSource.elapsedDisplayString(lastAccessed, now) + " ago, "
|
||||||
|
+ stateToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getState() {
|
||||||
|
return stateUpdater.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean compareAndSet(int expect, int update) {
|
||||||
|
return stateUpdater.compareAndSet(this, expect, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setState(int update) {
|
||||||
|
stateUpdater.set(this, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection close() {
|
||||||
|
ScheduledFuture<?> eol = endOfLife;
|
||||||
|
if (eol != null && !eol.isDone() && !eol.cancel(false)) {
|
||||||
|
logger.log(Level.WARNING, "maxLifeTime expiration task cancellation unexpectedly returned false for connection " + getPoolName() + " " + connection);
|
||||||
|
}
|
||||||
|
Connection con = connection;
|
||||||
|
connection = null;
|
||||||
|
endOfLife = null;
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stateToString() {
|
||||||
|
switch (state) {
|
||||||
|
case STATE_IN_USE:
|
||||||
|
return "IN_USE";
|
||||||
|
case STATE_NOT_IN_USE:
|
||||||
|
return "NOT_IN_USE";
|
||||||
|
case STATE_REMOVED:
|
||||||
|
return "REMOVED";
|
||||||
|
case STATE_RESERVED:
|
||||||
|
return "RESERVED";
|
||||||
|
default:
|
||||||
|
return "Invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class PoolEntryException extends Exception {
|
||||||
|
|
||||||
|
public PoolEntryException(Throwable t) {
|
||||||
|
super(t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class PoolInitializationException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an exception, possibly wrapping the provided Throwable as the cause.
|
||||||
|
*
|
||||||
|
* @param t the Throwable to wrap
|
||||||
|
*/
|
||||||
|
public PoolInitializationException(Throwable t) {
|
||||||
|
super("Failed to initialize pool: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,595 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
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.RowId;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the proxy class for java.sql.CallableStatement.
|
||||||
|
*/
|
||||||
|
public class ProxyCallableStatement extends ProxyPreparedStatement implements CallableStatement {
|
||||||
|
|
||||||
|
public ProxyCallableStatement(ProxyConnection connection, CallableStatement statement) {
|
||||||
|
super(connection, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wasNull() throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).wasNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getString(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBoolean(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBoolean(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getByte(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getByte(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getShort(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getShort(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getInt(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getLong(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getFloat(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDouble(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBigDecimal(parameterIndex, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBytes(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDate(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDate(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Time getTime(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTime(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getTimestamp(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTimestamp(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBigDecimal(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterIndex, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Ref getRef(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getRef(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob getBlob(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBlob(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clob getClob(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getClob(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Array getArray(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getArray(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDate(parameterIndex, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTime(parameterIndex, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTimestamp(parameterIndex, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterIndex, sqlType, typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(String parameterName, int sqlType) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException {
|
||||||
|
((CallableStatement) delegate).registerOutParameter(parameterName, sqlType, typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getURL(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getURL(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setURL(String parameterName, URL val) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setURL(parameterName, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(String parameterName, int sqlType) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNull(parameterName, sqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBoolean(String parameterName, boolean x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBoolean(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByte(String parameterName, byte x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setByte(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShort(String parameterName, short x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setShort(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInt(String parameterName, int x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setInt(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLong(String parameterName, long x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setLong(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFloat(String parameterName, float x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setFloat(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDouble(String parameterName, double x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setDouble(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBigDecimal(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setString(String parameterName, String x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setString(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytes(String parameterName, byte[] x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBytes(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(String parameterName, Date x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setDate(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(String parameterName, Time x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setTime(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(String parameterName, Timestamp x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setTimestamp(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setAsciiStream(parameterName, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBinaryStream(parameterName, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setObject(parameterName, x, targetSqlType, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setObject(parameterName, x, targetSqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(String parameterName, Object x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setObject(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setCharacterStream(parameterName, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setDate(parameterName, x, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setTime(parameterName, x, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setTimestamp(parameterName, x, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNull(parameterName, sqlType, typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getString(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBoolean(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBoolean(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getByte(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getByte(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short getShort(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getShort(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInt(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getInt(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getLong(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getFloat(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDouble(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBytes(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBytes(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDate(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDate(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Time getTime(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTime(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getTimestamp(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTimestamp(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimal(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBigDecimal(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(String parameterName, Map<String, Class<?>> map) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterName, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Ref getRef(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getRef(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob getBlob(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getBlob(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clob getClob(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getClob(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Array getArray(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getArray(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDate(String parameterName, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getDate(parameterName, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Time getTime(String parameterName, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTime(parameterName, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getTimestamp(parameterName, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getURL(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getURL(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RowId getRowId(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getRowId(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RowId getRowId(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getRowId(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRowId(String parameterName, RowId x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setRowId(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNString(String parameterName, String value) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNString(parameterName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNCharacterStream(parameterName, value, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(String parameterName, NClob value) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNClob(parameterName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(String parameterName, Reader reader, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setClob(parameterName, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBlob(parameterName, inputStream, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(String parameterName, Reader reader, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNClob(parameterName, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NClob getNClob(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNClob(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NClob getNClob(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNClob(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setSQLXML(parameterName, xmlObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLXML getSQLXML(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getSQLXML(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLXML getSQLXML(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getSQLXML(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNString(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNString(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNString(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNString(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getNCharacterStream(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNCharacterStream(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getNCharacterStream(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getNCharacterStream(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getCharacterStream(int parameterIndex) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getCharacterStream(parameterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getCharacterStream(String parameterName) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getCharacterStream(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(String parameterName, Blob x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBlob(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(String parameterName, Clob x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setClob(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setAsciiStream(parameterName, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBinaryStream(parameterName, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setCharacterStream(parameterName, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(String parameterName, InputStream x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setAsciiStream(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(String parameterName, InputStream x) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBinaryStream(parameterName, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(String parameterName, Reader reader) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setCharacterStream(parameterName, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(String parameterName, Reader value) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNCharacterStream(parameterName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(String parameterName, Reader reader) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setClob(parameterName, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(String parameterName, InputStream inputStream) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setBlob(parameterName, inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(String parameterName, Reader reader) throws SQLException {
|
||||||
|
((CallableStatement) delegate).setNClob(parameterName, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T getObject(int parameterIndex, Class<T> type) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterIndex, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T getObject(String parameterName, Class<T> type) throws SQLException {
|
||||||
|
return ((CallableStatement) delegate).getObject(parameterName, type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,697 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.FastList;
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
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.SQLClientInfoException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLTimeoutException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Savepoint;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Struct;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the proxy class for java.sql.Connection.
|
||||||
|
*/
|
||||||
|
public class ProxyConnection implements Connection {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ProxyConnection.class.getName());
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_READONLY = 0b000001;
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_AUTOCOMMIT = 0b000010;
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_ISOLATION = 0b000100;
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_CATALOG = 0b001000;
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_NETTIMEOUT = 0b010000;
|
||||||
|
|
||||||
|
private static final int DIRTY_BIT_SCHEMA = 0b100000;
|
||||||
|
|
||||||
|
private static final Set<String> ERROR_STATES;
|
||||||
|
|
||||||
|
private static final Set<Integer> ERROR_CODES;
|
||||||
|
|
||||||
|
private Connection delegate;
|
||||||
|
|
||||||
|
private final PoolEntry poolEntry;
|
||||||
|
|
||||||
|
private final ProxyLeakTask leakTask;
|
||||||
|
|
||||||
|
private final FastList<Statement> openStatements;
|
||||||
|
|
||||||
|
private int dirtyBits;
|
||||||
|
|
||||||
|
private long lastAccess;
|
||||||
|
|
||||||
|
private boolean isCommitStateDirty;
|
||||||
|
|
||||||
|
private boolean isReadOnly;
|
||||||
|
|
||||||
|
private boolean isAutoCommit;
|
||||||
|
|
||||||
|
private int networkTimeout;
|
||||||
|
|
||||||
|
private int transactionIsolation;
|
||||||
|
|
||||||
|
private String dbcatalog;
|
||||||
|
|
||||||
|
private String dbschema;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ERROR_STATES = new HashSet<>();
|
||||||
|
ERROR_STATES.add("0A000"); // FEATURE UNSUPPORTED
|
||||||
|
ERROR_STATES.add("57P01"); // ADMIN SHUTDOWN
|
||||||
|
ERROR_STATES.add("57P02"); // CRASH SHUTDOWN
|
||||||
|
ERROR_STATES.add("57P03"); // CANNOT CONNECT NOW
|
||||||
|
ERROR_STATES.add("01002"); // SQL92 disconnect error
|
||||||
|
ERROR_STATES.add("JZ0C0"); // Sybase disconnect error
|
||||||
|
ERROR_STATES.add("JZ0C1"); // Sybase disconnect error
|
||||||
|
|
||||||
|
ERROR_CODES = new HashSet<>();
|
||||||
|
ERROR_CODES.add(500150);
|
||||||
|
ERROR_CODES.add(2399);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyConnection(PoolEntry poolEntry,
|
||||||
|
Connection connection,
|
||||||
|
FastList<Statement> openStatements,
|
||||||
|
ProxyLeakTask leakTask,
|
||||||
|
long now,
|
||||||
|
boolean isReadOnly,
|
||||||
|
boolean isAutoCommit) {
|
||||||
|
this.poolEntry = poolEntry;
|
||||||
|
this.delegate = connection;
|
||||||
|
this.openStatements = openStatements;
|
||||||
|
this.leakTask = leakTask;
|
||||||
|
this.lastAccess = now;
|
||||||
|
this.isReadOnly = isReadOnly;
|
||||||
|
this.isAutoCommit = isAutoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCommitStateDirty() {
|
||||||
|
return isCommitStateDirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
return getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAutoCommitState() {
|
||||||
|
return isAutoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCatalogState() {
|
||||||
|
return dbcatalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSchemaState() {
|
||||||
|
return dbschema;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTransactionIsolationState() {
|
||||||
|
return transactionIsolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getReadOnlyState() {
|
||||||
|
return isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNetworkTimeoutState() {
|
||||||
|
return networkTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PoolEntry getPoolEntry() {
|
||||||
|
return poolEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLException checkException(SQLException sqle) {
|
||||||
|
boolean evict = false;
|
||||||
|
SQLException nse = sqle;
|
||||||
|
for (int depth = 0; delegate != CLOSED_CONNECTION && nse != null && depth < 10; depth++) {
|
||||||
|
final String sqlState = nse.getSQLState();
|
||||||
|
if (sqlState != null && sqlState.startsWith("08")
|
||||||
|
|| nse instanceof SQLTimeoutException
|
||||||
|
|| ERROR_STATES.contains(sqlState)
|
||||||
|
|| ERROR_CODES.contains(nse.getErrorCode())) {
|
||||||
|
// broken connection
|
||||||
|
evict = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nse = nse.getNextException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (evict) {
|
||||||
|
logger.log(Level.WARNING, "Connection marked as broken because of SQLSTATE(), ErrorCode(): " +
|
||||||
|
poolEntry.getPoolName() + " " + delegate + " " + nse.getSQLState() + " " + nse.getErrorCode(), nse);
|
||||||
|
leakTask.cancel();
|
||||||
|
poolEntry.evict("(connection is broken)");
|
||||||
|
delegate = CLOSED_CONNECTION;
|
||||||
|
}
|
||||||
|
return sqle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void untrackStatement(final Statement statement) {
|
||||||
|
openStatements.remove(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markCommitStateDirty() {
|
||||||
|
if (isAutoCommit) {
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
} else {
|
||||||
|
isCommitStateDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelLeakTask() {
|
||||||
|
leakTask.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized <T extends Statement> T trackStatement(final T statement) {
|
||||||
|
openStatements.add(statement);
|
||||||
|
return statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("try")
|
||||||
|
private synchronized void closeStatements() {
|
||||||
|
final int size = openStatements.size();
|
||||||
|
if (size > 0) {
|
||||||
|
for (int i = 0; i < size && delegate != CLOSED_CONNECTION; i++) {
|
||||||
|
try (Statement ignored = openStatements.get(i)) {
|
||||||
|
// automatic resource cleanup
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logger.log(Level.WARNING, "Connection marked as broken because of an exception closing open statements during Connection.close(): " +
|
||||||
|
poolEntry.getPoolName() + " " + delegate);
|
||||||
|
leakTask.cancel();
|
||||||
|
poolEntry.evict("(exception closing Statements during Connection.close())");
|
||||||
|
delegate = CLOSED_CONNECTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openStatements.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closeStatements();
|
||||||
|
if (delegate != CLOSED_CONNECTION) {
|
||||||
|
leakTask.cancel();
|
||||||
|
try {
|
||||||
|
if (isCommitStateDirty && !isAutoCommit) {
|
||||||
|
delegate.rollback();
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
logger.log(Level.FINE, "Executed rollback on connection due to dirty commit state on close(): " + poolEntry.getPoolName() + " " + delegate);
|
||||||
|
}
|
||||||
|
if (dirtyBits != 0) {
|
||||||
|
//poolEntry.resetConnectionState(this, dirtyBits);
|
||||||
|
resetConnectionState(poolEntry.getConnection(), dirtyBits,
|
||||||
|
poolEntry.getPool().getConfig().getCatalog() ,
|
||||||
|
poolEntry.getPool().getConfig().getSchema());
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
}
|
||||||
|
delegate.clearWarnings();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// when connections are aborted, exceptions are often thrown that should not reach the application
|
||||||
|
if (!poolEntry.isMarkedEvicted()) {
|
||||||
|
throw checkException(e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
delegate = CLOSED_CONNECTION;
|
||||||
|
poolEntry.recycle(lastAccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return delegate == CLOSED_CONNECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int concurrency) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int concurrency, int holdability) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement(resultSetType, concurrency, holdability)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String nativeSQL(String sql) throws SQLException {
|
||||||
|
return delegate.nativeSQL(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int concurrency) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Class<?>> getTypeMap() throws SQLException {
|
||||||
|
return delegate.getTypeMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
|
||||||
|
delegate.setTypeMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHoldability(int holdability) throws SQLException {
|
||||||
|
delegate.setHoldability(holdability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHoldability() throws SQLException {
|
||||||
|
return delegate.getHoldability();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint() throws SQLException {
|
||||||
|
return delegate.setSavepoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint(String name) throws SQLException {
|
||||||
|
return delegate.setSavepoint(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, autoGeneratedKeys)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int concurrency, int holdability) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, resultSetType, concurrency, holdability)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnIndexes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql, columnNames)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clob createClob() throws SQLException {
|
||||||
|
return delegate.createClob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blob createBlob() throws SQLException {
|
||||||
|
return delegate.createBlob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NClob createNClob() throws SQLException {
|
||||||
|
return delegate.createNClob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLXML createSQLXML() throws SQLException {
|
||||||
|
return delegate.createSQLXML();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(int timeout) throws SQLException {
|
||||||
|
return delegate.isValid(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(String name, String value) throws SQLClientInfoException {
|
||||||
|
delegate.setClientInfo(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(Properties properties) throws SQLClientInfoException {
|
||||||
|
delegate.setClientInfo(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientInfo(String name) throws SQLException {
|
||||||
|
return delegate.getClientInfo(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getClientInfo() throws SQLException {
|
||||||
|
return delegate.getClientInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
|
||||||
|
return delegate.createArrayOf(typeName, elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
|
||||||
|
return delegate.createStruct(typeName, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DatabaseMetaData getMetaData() throws SQLException {
|
||||||
|
markCommitStateDirty();
|
||||||
|
return ProxyFactory.getProxyDatabaseMetaData(this, delegate.getMetaData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void commit() throws SQLException {
|
||||||
|
delegate.commit();
|
||||||
|
isCommitStateDirty = false;
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void rollback() throws SQLException {
|
||||||
|
delegate.rollback();
|
||||||
|
isCommitStateDirty = false;
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void rollback(Savepoint savepoint) throws SQLException {
|
||||||
|
delegate.rollback(savepoint);
|
||||||
|
isCommitStateDirty = false;
|
||||||
|
lastAccess = ClockSource.currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
|
||||||
|
delegate.releaseSavepoint(savepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAutoCommit(boolean autoCommit) throws SQLException {
|
||||||
|
delegate.setAutoCommit(autoCommit);
|
||||||
|
isAutoCommit = autoCommit;
|
||||||
|
dirtyBits |= DIRTY_BIT_AUTOCOMMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAutoCommit() throws SQLException {
|
||||||
|
return delegate.getAutoCommit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setReadOnly(boolean readOnly) throws SQLException {
|
||||||
|
delegate.setReadOnly(readOnly);
|
||||||
|
isReadOnly = readOnly;
|
||||||
|
isCommitStateDirty = false;
|
||||||
|
dirtyBits |= DIRTY_BIT_READONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() throws SQLException {
|
||||||
|
return delegate.isReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTransactionIsolation(int level) throws SQLException {
|
||||||
|
delegate.setTransactionIsolation(level);
|
||||||
|
transactionIsolation = level;
|
||||||
|
dirtyBits |= DIRTY_BIT_ISOLATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTransactionIsolation() throws SQLException {
|
||||||
|
return delegate.getTransactionIsolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
return delegate.getWarnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
delegate.clearWarnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCatalog(String catalog) throws SQLException {
|
||||||
|
delegate.setCatalog(catalog);
|
||||||
|
dbcatalog = catalog;
|
||||||
|
dirtyBits |= DIRTY_BIT_CATALOG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCatalog() throws SQLException {
|
||||||
|
return delegate.getCatalog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
|
||||||
|
delegate.setNetworkTimeout(executor, milliseconds);
|
||||||
|
networkTimeout = milliseconds;
|
||||||
|
dirtyBits |= DIRTY_BIT_NETTIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNetworkTimeout() throws SQLException {
|
||||||
|
return delegate.getNetworkTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setSchema(String schema) throws SQLException {
|
||||||
|
delegate.setSchema(schema);
|
||||||
|
dbschema = schema;
|
||||||
|
dirtyBits |= DIRTY_BIT_SCHEMA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return delegate.getSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort(Executor executor) throws SQLException {
|
||||||
|
delegate.abort(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return iface.isInstance(delegate) || (delegate != null && delegate.isWrapperFor(iface));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(delegate)) {
|
||||||
|
return (T) delegate;
|
||||||
|
} else if (delegate != null) {
|
||||||
|
return delegate.unwrap(iface);
|
||||||
|
}
|
||||||
|
throw new SQLException("Wrapped connection is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Connection CLOSED_CONNECTION = getClosedConnection();
|
||||||
|
|
||||||
|
private static Connection getClosedConnection() {
|
||||||
|
InvocationHandler handler = (proxy, method, args) -> {
|
||||||
|
final String methodName = method.getName();
|
||||||
|
if ("isClosed".equals(methodName)) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
} else if ("isValid".equals(methodName)) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
if ("abort".equals(methodName)) {
|
||||||
|
return Void.TYPE;
|
||||||
|
}
|
||||||
|
if ("close".equals(methodName)) {
|
||||||
|
return Void.TYPE;
|
||||||
|
} else if ("toString".equals(methodName)) {
|
||||||
|
return ProxyConnection.class.getCanonicalName();
|
||||||
|
}
|
||||||
|
throw new SQLException("connection is closed");
|
||||||
|
};
|
||||||
|
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
|
||||||
|
new Class<?>[] { Connection.class }, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetConnectionState(Connection connection, int dirtyBits, String catalog, String schema) throws SQLException {
|
||||||
|
int resetBits = 0;
|
||||||
|
if ((dirtyBits & DIRTY_BIT_READONLY) != 0 && getReadOnlyState() != isReadOnly) {
|
||||||
|
connection.setReadOnly(isReadOnly);
|
||||||
|
resetBits |= DIRTY_BIT_READONLY;
|
||||||
|
}
|
||||||
|
if ((dirtyBits & DIRTY_BIT_AUTOCOMMIT) != 0 && getAutoCommitState() != isAutoCommit) {
|
||||||
|
connection.setAutoCommit(isAutoCommit);
|
||||||
|
resetBits |= DIRTY_BIT_AUTOCOMMIT;
|
||||||
|
}
|
||||||
|
if ((dirtyBits & DIRTY_BIT_ISOLATION) != 0 && getTransactionIsolationState() != transactionIsolation) {
|
||||||
|
connection.setTransactionIsolation(transactionIsolation);
|
||||||
|
resetBits |= DIRTY_BIT_ISOLATION;
|
||||||
|
}
|
||||||
|
if ((dirtyBits & DIRTY_BIT_CATALOG) != 0 && catalog != null && !catalog.equals(getCatalogState())) {
|
||||||
|
connection.setCatalog(catalog);
|
||||||
|
resetBits |= DIRTY_BIT_CATALOG;
|
||||||
|
}
|
||||||
|
if ((dirtyBits & DIRTY_BIT_NETTIMEOUT) != 0 && getNetworkTimeoutState() != networkTimeout) {
|
||||||
|
connection.setNetworkTimeout(Runnable::run, networkTimeout);
|
||||||
|
resetBits |= DIRTY_BIT_NETTIMEOUT;
|
||||||
|
}
|
||||||
|
if ((dirtyBits & DIRTY_BIT_SCHEMA) != 0 && schema != null && !schema.equals(getSchemaState())) {
|
||||||
|
connection.setSchema(schema);
|
||||||
|
resetBits |= DIRTY_BIT_SCHEMA;
|
||||||
|
}
|
||||||
|
if (resetBits != 0 && logger.isLoggable(Level.FINE)) {
|
||||||
|
final String string = stringFromResetBits(resetBits);
|
||||||
|
logger.log(Level.FINE, () -> "reset on connection: " + string + " " + connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will create a string for debug logging. Given a set of "reset bits", this
|
||||||
|
* method will return a concatenated string, for example
|
||||||
|
* Input : 0b00110
|
||||||
|
* Output: "autoCommit, isolation"
|
||||||
|
*
|
||||||
|
* @param bits a set of "reset bits"
|
||||||
|
* @return a string of which states were reset
|
||||||
|
*/
|
||||||
|
private String stringFromResetBits(final int bits) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
for (int ndx = 0; ndx < RESET_STATES.length; ndx++) {
|
||||||
|
if ((bits & (0b1 << ndx)) != 0) {
|
||||||
|
sb.append(RESET_STATES[ndx]).append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.setLength(sb.length() - 2); // trim trailing comma
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] RESET_STATES = {"readOnly", "autoCommit", "isolation", "catalog", "netTimeout", "schema"};
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,68 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.connection.pool.util.FastList;
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory class that produces proxies around instances of the standard JDBC interfaces.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ProxyFactory {
|
||||||
|
|
||||||
|
private ProxyFactory() {
|
||||||
|
// unconstructable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a proxy for the specified {@link Connection} instance.
|
||||||
|
*
|
||||||
|
* @param poolEntry the PoolEntry holding pool state
|
||||||
|
* @param connection the raw database Connection
|
||||||
|
* @param openStatements a reusable list to track open Statement instances
|
||||||
|
* @param leakTask the ProxyLeakTask for this connection
|
||||||
|
* @param now the current timestamp
|
||||||
|
* @param isReadOnly the default readOnly state of the connection
|
||||||
|
* @param isAutoCommit the default autoCommit state of the connection
|
||||||
|
* @return a proxy that wraps the specified {@link Connection}
|
||||||
|
*/
|
||||||
|
public static Connection getProxyConnection(PoolEntry poolEntry,
|
||||||
|
Connection connection,
|
||||||
|
FastList<Statement> openStatements,
|
||||||
|
ProxyLeakTask leakTask,
|
||||||
|
long now,
|
||||||
|
boolean isReadOnly,
|
||||||
|
boolean isAutoCommit) {
|
||||||
|
return new ProxyConnection(poolEntry, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Statement getProxyStatement(ProxyConnection connection,
|
||||||
|
Statement statement) {
|
||||||
|
return new ProxyStatement(connection, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CallableStatement getProxyCallableStatement(ProxyConnection connection,
|
||||||
|
CallableStatement statement) {
|
||||||
|
return new ProxyCallableStatement(connection, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PreparedStatement getProxyPreparedStatement(ProxyConnection connection,
|
||||||
|
PreparedStatement statement) {
|
||||||
|
return new ProxyPreparedStatement(connection, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResultSet getProxyResultSet(ProxyConnection connection,
|
||||||
|
ProxyStatement statement,
|
||||||
|
ResultSet resultSet) {
|
||||||
|
return new ProxyResultSet(connection, statement, resultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DatabaseMetaData getProxyDatabaseMetaData(ProxyConnection connection,
|
||||||
|
DatabaseMetaData metaData) {
|
||||||
|
return new ProxyDatabaseMetaData(connection, metaData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Runnable that is scheduled in the future to report leaks.
|
||||||
|
* The ScheduledFuture is cancelled if the connection is closed before the leak time expires.
|
||||||
|
*/
|
||||||
|
public class ProxyLeakTask implements Runnable {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ProxyLeakTask.class.getName());
|
||||||
|
|
||||||
|
public static final ProxyLeakTask NO_LEAK;
|
||||||
|
|
||||||
|
private ScheduledFuture<?> scheduledFuture;
|
||||||
|
|
||||||
|
private String connectionName;
|
||||||
|
|
||||||
|
private Exception exception;
|
||||||
|
|
||||||
|
private String threadName;
|
||||||
|
|
||||||
|
private boolean isLeaked;
|
||||||
|
|
||||||
|
static {
|
||||||
|
NO_LEAK = new ProxyLeakTask() {
|
||||||
|
@Override
|
||||||
|
public void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyLeakTask(final PoolEntry poolEntry) {
|
||||||
|
this.exception = new Exception("Apparent connection leak detected");
|
||||||
|
this.threadName = Thread.currentThread().getName();
|
||||||
|
this.connectionName = poolEntry.getConnection().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProxyLeakTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void schedule(ScheduledExecutorService executorService, long leakDetectionThreshold) {
|
||||||
|
scheduledFuture = executorService.schedule(this, leakDetectionThreshold, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
isLeaked = true;
|
||||||
|
final StackTraceElement[] stackTrace = exception.getStackTrace();
|
||||||
|
final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
|
||||||
|
System.arraycopy(stackTrace, 5, trace, 0, trace.length);
|
||||||
|
exception.setStackTrace(trace);
|
||||||
|
logger.log(Level.WARNING, "Connection leak detection triggered for on thread, stack trace follows: " + connectionName + " " + threadName, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
scheduledFuture.cancel(false);
|
||||||
|
if (isLeaked) {
|
||||||
|
logger.log(Level.INFO, "Previously reported leaked connection on thread was returned to the pool (unleaked: )" + connectionName + " " + threadName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link ProxyLeakTask} Runnables that are scheduled in the future to report leaks.
|
||||||
|
*/
|
||||||
|
public class ProxyLeakTaskFactory {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private long leakDetectionThreshold;
|
||||||
|
|
||||||
|
public ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService) {
|
||||||
|
this.executorService = executorService;
|
||||||
|
this.leakDetectionThreshold = leakDetectionThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyLeakTask schedule(final PoolEntry poolEntry) {
|
||||||
|
return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLeakDetectionThreshold(final long leakDetectionThreshold) {
|
||||||
|
this.leakDetectionThreshold = leakDetectionThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
|
||||||
|
ProxyLeakTask task = new ProxyLeakTask(poolEntry);
|
||||||
|
task.schedule(executorService, leakDetectionThreshold);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
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.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.SQLXML;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the proxy class for java.sql.PreparedStatement.
|
||||||
|
*/
|
||||||
|
public class ProxyPreparedStatement extends ProxyStatement implements PreparedStatement {
|
||||||
|
|
||||||
|
public ProxyPreparedStatement(ProxyConnection connection, PreparedStatement statement) {
|
||||||
|
super(connection, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return ((PreparedStatement) delegate).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addBatch() throws SQLException {
|
||||||
|
((PreparedStatement) delegate).addBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRef(int parameterIndex, Ref x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setRef(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, Blob x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBlob(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Clob x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setClob(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setArray(int parameterIndex, Array x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setArray(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultSetMetaData getMetaData() throws SQLException {
|
||||||
|
return ((PreparedStatement) delegate).getMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setDate(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setTime(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setTimestamp(parameterIndex, x, cal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNull(parameterIndex, sqlType, typeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setURL(int parameterIndex, URL x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setURL(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterMetaData getParameterMetaData() throws SQLException {
|
||||||
|
return ((PreparedStatement) delegate).getParameterMetaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRowId(int parameterIndex, RowId x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setRowId(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNString(int parameterIndex, String value) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNString(parameterIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNCharacterStream(parameterIndex, value, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, NClob value) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNClob(parameterIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setClob(parameterIndex, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBlob(parameterIndex, inputStream, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNClob(parameterIndex, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setSQLXML(parameterIndex, xmlObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setObject(parameterIndex, x, targetSqlType, scaleOrLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setCharacterStream(parameterIndex, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNCharacterStream(parameterIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setClob(parameterIndex, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBlob(parameterIndex, inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNClob(parameterIndex, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
ResultSet resultSet = ((PreparedStatement) getDelegate()).executeQuery();
|
||||||
|
return ProxyFactory.getProxyResultSet(connection, this, resultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return ((PreparedStatement) getDelegate()).executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setNull(parameterIndex, sqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBoolean(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByte(int parameterIndex, byte x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setByte(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShort(int parameterIndex, short x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setShort(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInt(int parameterIndex, int x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setInt(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLong(int parameterIndex, long x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setLong(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFloat(int parameterIndex, float x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setFloat(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDouble(int parameterIndex, double x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setDouble(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBigDecimal(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setString(int parameterIndex, String x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setString(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBytes(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setDate(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setTime(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setTimestamp(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setAsciiStream(parameterIndex, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setUnicodeStream(parameterIndex, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setBinaryStream(parameterIndex, x, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearParameters() throws SQLException {
|
||||||
|
((PreparedStatement) delegate).clearParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setObject(parameterIndex, x, targetSqlType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x) throws SQLException {
|
||||||
|
((PreparedStatement) delegate).setObject(parameterIndex, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return ((PreparedStatement) getDelegate()).executeLargeUpdate();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,394 @@
|
||||||
|
package org.xbib.jdbc.connection.pool;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the proxy class for java.sql.Statement.
|
||||||
|
*/
|
||||||
|
public class ProxyStatement implements Statement {
|
||||||
|
|
||||||
|
protected final ProxyConnection connection;
|
||||||
|
|
||||||
|
protected final Statement delegate;
|
||||||
|
|
||||||
|
private boolean isClosed;
|
||||||
|
|
||||||
|
private ResultSet proxyResultSet;
|
||||||
|
|
||||||
|
public ProxyStatement(ProxyConnection connection, Statement statement) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.delegate = statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Statement getDelegate() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public SQLException checkException(SQLException e) {
|
||||||
|
return connection.checkException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final String delegateToString = delegate.toString();
|
||||||
|
return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegateToString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
synchronized (this) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isClosed = true;
|
||||||
|
}
|
||||||
|
connection.untrackStatement(delegate);
|
||||||
|
try {
|
||||||
|
delegate.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw connection.checkException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxFieldSize() throws SQLException {
|
||||||
|
return delegate.getMaxFieldSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxFieldSize(int max) throws SQLException {
|
||||||
|
delegate.setMaxFieldSize(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxRows() throws SQLException {
|
||||||
|
return delegate.getMaxRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMaxRows(int max) throws SQLException {
|
||||||
|
delegate.setMaxRows(max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEscapeProcessing(boolean enable) throws SQLException {
|
||||||
|
delegate.setEscapeProcessing(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getQueryTimeout() throws SQLException {
|
||||||
|
return delegate.getQueryTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setQueryTimeout(int seconds) throws SQLException {
|
||||||
|
delegate.setQueryTimeout(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() throws SQLException {
|
||||||
|
delegate.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
return delegate.getWarnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
delegate.clearWarnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCursorName(String name) throws SQLException {
|
||||||
|
delegate.setCursorName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults(int current) throws SQLException {
|
||||||
|
return delegate.getMoreResults(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.execute(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.execute(sql, autoGeneratedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery(String sql) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
ResultSet resultSet = delegate.executeQuery(sql);
|
||||||
|
return ProxyFactory.getProxyResultSet(connection, this, resultSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeUpdate(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int[] executeBatch() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeUpdate(sql, autoGeneratedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeUpdate(sql, columnIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeUpdate(sql, columnNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.execute(sql, columnIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, String[] columnNames) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.execute(sql, columnNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResultSetHoldability() throws SQLException {
|
||||||
|
return delegate.getResultSetHoldability();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return delegate.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPoolable(boolean poolable) throws SQLException {
|
||||||
|
delegate.setPoolable(poolable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPoolable() throws SQLException {
|
||||||
|
return delegate.isPoolable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeOnCompletion() throws SQLException {
|
||||||
|
delegate.closeOnCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCloseOnCompletion() throws SQLException {
|
||||||
|
return delegate.isCloseOnCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long[] executeLargeBatch() throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeLargeBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeLargeUpdate(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeLargeUpdate(sql, autoGeneratedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeLargeUpdate(sql, columnIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
connection.markCommitStateDirty();
|
||||||
|
return delegate.executeLargeUpdate(sql, columnNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getResultSet() throws SQLException {
|
||||||
|
final ResultSet resultSet = delegate.getResultSet();
|
||||||
|
if (resultSet != null) {
|
||||||
|
if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).getDelegate() != resultSet) {
|
||||||
|
proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxyResultSet = null;
|
||||||
|
}
|
||||||
|
return proxyResultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateCount() throws SQLException {
|
||||||
|
return delegate.getUpdateCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults() throws SQLException {
|
||||||
|
return delegate.getMoreResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFetchDirection(int direction) throws SQLException {
|
||||||
|
delegate.setFetchDirection(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFetchDirection() throws SQLException {
|
||||||
|
return delegate.getFetchDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFetchSize(int rows) throws SQLException {
|
||||||
|
delegate.setFetchSize(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFetchSize() throws SQLException {
|
||||||
|
return delegate.getFetchSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResultSetConcurrency() throws SQLException {
|
||||||
|
return delegate.getResultSetConcurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResultSetType() throws SQLException {
|
||||||
|
return delegate.getResultSetType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addBatch(String sql) throws SQLException {
|
||||||
|
delegate.addBatch(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearBatch() throws SQLException {
|
||||||
|
delegate.clearBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getGeneratedKeys() throws SQLException {
|
||||||
|
ResultSet resultSet = delegate.getGeneratedKeys();
|
||||||
|
if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).getDelegate() != resultSet) {
|
||||||
|
proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
|
||||||
|
}
|
||||||
|
return proxyResultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public final <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(delegate)) {
|
||||||
|
return (T) delegate;
|
||||||
|
} else if (delegate != null) {
|
||||||
|
return delegate.unwrap(iface);
|
||||||
|
}
|
||||||
|
throw new SQLException("Wrapped statement is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return delegate.isWrapperFor(iface);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a specialized concurrent bag that achieves superior performance
|
||||||
|
* to {@link java.util.concurrent.LinkedBlockingQueue} and
|
||||||
|
* {@link java.util.concurrent.LinkedTransferQueue} for the purposes of a
|
||||||
|
* connection pool. It uses {@link ThreadLocal} storage when possible to avoid
|
||||||
|
* locks, but resorts to scanning a common collection if there are no
|
||||||
|
* available items in the {@link ThreadLocal} list. Not-in-use items in the
|
||||||
|
* {@link ThreadLocal} lists can be "stolen" when the borrowing thread has none
|
||||||
|
* of its own. It is a "lock-less" implementation using a specialized
|
||||||
|
* {@link AbstractQueuedLongSynchronizer} to manage cross-thread signaling.
|
||||||
|
* Note that items that are "borrowed" from the bag are not actually
|
||||||
|
* removed from any collection, so garbage collection will not occur
|
||||||
|
* even if the reference is abandoned. Thus care must be taken to
|
||||||
|
* {@link Bag#requite(T)} borrowed objects otherwise a memory leak will result.
|
||||||
|
* Only the {@link Bag#remove(T)} method can completely remove an object.
|
||||||
|
*
|
||||||
|
* @param <T> the templated type to store in the bag
|
||||||
|
*/
|
||||||
|
public class Bag<T extends BagEntry> implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(Bag.class.getName());
|
||||||
|
|
||||||
|
private final CopyOnWriteArrayList<T> sharedList;
|
||||||
|
|
||||||
|
private final boolean weakThreadLocals;
|
||||||
|
|
||||||
|
private final ThreadLocal<List<Object>> threadList;
|
||||||
|
|
||||||
|
private final BagStateListener listener;
|
||||||
|
|
||||||
|
private final AtomicInteger waiters;
|
||||||
|
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
private final SynchronousQueue<T> handoffQueue;
|
||||||
|
|
||||||
|
private String lastMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Bag with the specified listener.
|
||||||
|
*
|
||||||
|
* @param listener the IBagStateListener to attach to this bag
|
||||||
|
*/
|
||||||
|
public Bag(BagStateListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.weakThreadLocals = useWeakThreadLocals();
|
||||||
|
this.handoffQueue = new SynchronousQueue<>(true);
|
||||||
|
this.waiters = new AtomicInteger();
|
||||||
|
this.sharedList = new CopyOnWriteArrayList<>();
|
||||||
|
if (weakThreadLocals) {
|
||||||
|
this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
|
||||||
|
} else {
|
||||||
|
this.threadList = ThreadLocal.withInitial(() -> new FastList<>(BagEntry.class, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastMessage() {
|
||||||
|
return lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method will borrow a BagEntry from the bag, blocking for the
|
||||||
|
* specified timeout if none are available.
|
||||||
|
*
|
||||||
|
* @param timeout how long to wait before giving up, in units of unit
|
||||||
|
* @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
|
||||||
|
* @return a borrowed instance from the bag or null if a timeout occurs
|
||||||
|
* @throws InterruptedException if interrupted while waiting
|
||||||
|
*/
|
||||||
|
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
|
||||||
|
// Try the thread-local list first
|
||||||
|
final List<Object> list = threadList.get();
|
||||||
|
for (int i = list.size() - 1; i >= 0; i--) {
|
||||||
|
final Object entry = list.remove(i);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
|
||||||
|
if (bagEntry != null && bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
|
||||||
|
return bagEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int waiting = waiters.incrementAndGet();
|
||||||
|
try {
|
||||||
|
for (T bagEntry : sharedList) {
|
||||||
|
if (bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
|
||||||
|
if (waiting > 1) {
|
||||||
|
listener.addBagItem(waiting - 1);
|
||||||
|
}
|
||||||
|
return bagEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listener.addBagItem(waiting);
|
||||||
|
timeout = timeUnit.toNanos(timeout);
|
||||||
|
do {
|
||||||
|
final long start = ClockSource.currentTime();
|
||||||
|
final T bagEntry = handoffQueue.poll(timeout, TimeUnit.NANOSECONDS);
|
||||||
|
if (bagEntry == null || bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_IN_USE)) {
|
||||||
|
return bagEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout -= ClockSource.elapsedNanos(start);
|
||||||
|
} while (timeout > 10_000);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
waiters.decrementAndGet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will return a borrowed object to the bag. Objects
|
||||||
|
* that are borrowed from the bag but never "requited" will result
|
||||||
|
* in a memory leak.
|
||||||
|
*
|
||||||
|
* @param bagEntry the value to return to the bag
|
||||||
|
* @throws NullPointerException if value is null
|
||||||
|
* @throws IllegalStateException if the bagEntry was not borrowed from the bag
|
||||||
|
*/
|
||||||
|
public void requite(final T bagEntry) {
|
||||||
|
bagEntry.setState(BagEntry.STATE_NOT_IN_USE);
|
||||||
|
for (int i = 0; waiters.get() > 0; i++) {
|
||||||
|
if (bagEntry.getState() != BagEntry.STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
|
||||||
|
return;
|
||||||
|
} else if ((i & 0xff) == 0xff) {
|
||||||
|
LockSupport.parkNanos(TimeUnit.MICROSECONDS.toNanos(10));
|
||||||
|
} else {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<Object> threadLocalList = threadList.get();
|
||||||
|
if (threadLocalList.size() < 50) {
|
||||||
|
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new object to the bag for others to borrow.
|
||||||
|
*
|
||||||
|
* @param bagEntry an object to add to the bag
|
||||||
|
*/
|
||||||
|
public void add(final T bagEntry) {
|
||||||
|
if (closed) {
|
||||||
|
lastMessage = "Bag has been closed, ignoring add()";
|
||||||
|
logger.info(lastMessage);
|
||||||
|
throw new IllegalStateException("Bag has been closed, ignoring add()");
|
||||||
|
}
|
||||||
|
sharedList.add(bagEntry);
|
||||||
|
// spin until a thread takes it or none are waiting
|
||||||
|
while (waiters.get() > 0 && bagEntry.getState() == BagEntry.STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a value from the bag. This method should only be called
|
||||||
|
* with objects obtained by {@link Bag#borrow(long, TimeUnit)} or
|
||||||
|
* {@link Bag#reserve(T)}.
|
||||||
|
*
|
||||||
|
* @param bagEntry the value to remove
|
||||||
|
* @return true if the entry was removed, false otherwise
|
||||||
|
* @throws IllegalStateException if an attempt is made to remove an object
|
||||||
|
* from the bag that was not borrowed or reserved first
|
||||||
|
*/
|
||||||
|
public boolean remove(final T bagEntry) {
|
||||||
|
if (!bagEntry.compareAndSet(BagEntry.STATE_IN_USE, BagEntry.STATE_REMOVED) &&
|
||||||
|
!bagEntry.compareAndSet(BagEntry.STATE_RESERVED, BagEntry.STATE_REMOVED) && !closed) {
|
||||||
|
lastMessage = "attempt to remove an object from the bag that was not borrowed or reserved: " + bagEntry;
|
||||||
|
logger.warning(lastMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean removed = sharedList.remove(bagEntry);
|
||||||
|
if (!removed && !closed) {
|
||||||
|
lastMessage = "attempt to remove an object from the bag that does not exist: " + bagEntry;
|
||||||
|
logger.warning(lastMessage);
|
||||||
|
}
|
||||||
|
threadList.get().remove(bagEntry);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the bag to further adds.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method provides a "snapshot" in time of the BagEntry
|
||||||
|
* items in the bag in the specified state. It does not "lock"
|
||||||
|
* or reserve items in any way. Call {@link Bag#reserve(T)}
|
||||||
|
* on items in list before performing any action on them.
|
||||||
|
*
|
||||||
|
* @param state one of the {@link BagEntry} states
|
||||||
|
* @return a possibly empty list of objects having the state specified
|
||||||
|
*/
|
||||||
|
public List<T> values(int state) {
|
||||||
|
final List<T> list = sharedList.stream()
|
||||||
|
.filter(e -> e.getState() == state)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Collections.reverse(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method provides a "snapshot" in time of the bag items. It
|
||||||
|
* does not "lock" or reserve items in any way. Call {@link Bag#reserve(T)}
|
||||||
|
* on items in the list, or understand the concurrency implications of
|
||||||
|
* modifying items, before performing any action on them.
|
||||||
|
*
|
||||||
|
* @return a possibly empty list of (all) bag items
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public List<T> values() {
|
||||||
|
return (List<T>) sharedList.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method is used to make an item in the bag "unavailable" for
|
||||||
|
* borrowing. It is primarily used when wanting to operate on items
|
||||||
|
* returned by the {@link Bag#values(int)} method. Items that are
|
||||||
|
* reserved can be removed from the bag via {@link Bag#remove(T)}
|
||||||
|
* without the need to unreserve them. Items that are not removed
|
||||||
|
* from the bag can be make available for borrowing again by calling
|
||||||
|
* the {@link Bag#unreserve(BagEntry)} method.
|
||||||
|
*
|
||||||
|
* @param bagEntry the item to reserve
|
||||||
|
* @return true if the item was able to be reserved, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean reserve(T bagEntry) {
|
||||||
|
return bagEntry.compareAndSet(BagEntry.STATE_NOT_IN_USE, BagEntry.STATE_RESERVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to make an item reserved via {@link Bag#reserve(T)}
|
||||||
|
* available again for borrowing.
|
||||||
|
*
|
||||||
|
* @param bagEntry the item to unreserve
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
|
public void unreserve(final T bagEntry) {
|
||||||
|
if (bagEntry.compareAndSet(BagEntry.STATE_RESERVED, BagEntry.STATE_NOT_IN_USE)) {
|
||||||
|
// spin until a thread takes it or none are waiting
|
||||||
|
while (waiters.get() > 0 && !handoffQueue.offer(bagEntry)) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastMessage = "attempt to relinquish an object to the bag that was not reserved: " + bagEntry;
|
||||||
|
logger.warning(lastMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of threads pending (waiting) for an item from the
|
||||||
|
* bag to become available.
|
||||||
|
*
|
||||||
|
* @return the number of threads waiting for items from the bag
|
||||||
|
*/
|
||||||
|
public int getWaitingThreadCount() {
|
||||||
|
return waiters.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a count of the number of items in the specified state at the time of this call.
|
||||||
|
*
|
||||||
|
* @param state the state of the items to count
|
||||||
|
* @return a count of how many items in the bag are in the specified state
|
||||||
|
*/
|
||||||
|
public int getCount(final int state) {
|
||||||
|
int count = 0;
|
||||||
|
for (BagEntry e : sharedList) {
|
||||||
|
if (e.getState() == state) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getStateCounts() {
|
||||||
|
final int[] states = new int[6];
|
||||||
|
for (BagEntry e : sharedList) {
|
||||||
|
++states[e.getState()];
|
||||||
|
}
|
||||||
|
states[4] = sharedList.size();
|
||||||
|
states[5] = waiters.get();
|
||||||
|
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total number of items in the bag.
|
||||||
|
*
|
||||||
|
* @return the number of items in the bag
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return sharedList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dumpState() {
|
||||||
|
sharedList.forEach(entry -> logger.info(entry.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether to use WeakReferences based on whether there is a
|
||||||
|
* custom ClassLoader implementation sitting between this class and the
|
||||||
|
* System ClassLoader.
|
||||||
|
*
|
||||||
|
* @return true if we should use WeakReferences in our ThreadLocals, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean useWeakThreadLocals() {
|
||||||
|
try {
|
||||||
|
if (System.getProperty("pool.jdbc.useWeakReferences") != null) {
|
||||||
|
return Boolean.getBoolean("pool.jdbc.useWeakReferences");
|
||||||
|
}
|
||||||
|
return getClass().getClassLoader() != ClassLoader.getSystemClassLoader();
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
public interface BagEntry {
|
||||||
|
int STATE_NOT_IN_USE = 0;
|
||||||
|
int STATE_IN_USE = 1;
|
||||||
|
int STATE_REMOVED = -1;
|
||||||
|
int STATE_RESERVED = -2;
|
||||||
|
|
||||||
|
boolean compareAndSet(int expectState, int newState);
|
||||||
|
|
||||||
|
void setState(int newState);
|
||||||
|
|
||||||
|
int getState();
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
public interface BagStateListener {
|
||||||
|
|
||||||
|
void addBagItem(int waiting);
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A resolution-independent provider of current time-stamps and elapsed time
|
||||||
|
* calculations.
|
||||||
|
*/
|
||||||
|
public interface ClockSource {
|
||||||
|
ClockSource CLOCK = ClockSourceFactory.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current time-stamp (resolution is opaque).
|
||||||
|
*
|
||||||
|
* @return the current time-stamp
|
||||||
|
*/
|
||||||
|
static long currentTime() {
|
||||||
|
return CLOCK.currentTime0();
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTime0();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an opaque time-stamp returned by currentTime() into
|
||||||
|
* milliseconds.
|
||||||
|
*
|
||||||
|
* @param time an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the time-stamp in milliseconds
|
||||||
|
*/
|
||||||
|
static long toMillis(long time) {
|
||||||
|
return CLOCK.toMillis0(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
long toMillis0(long time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an opaque time-stamp returned by currentTime() into
|
||||||
|
* nanoseconds.
|
||||||
|
*
|
||||||
|
* @param time an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the time-stamp in nanoseconds
|
||||||
|
*/
|
||||||
|
static long toNanos(long time) {
|
||||||
|
return CLOCK.toNanos0(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
long toNanos0(long time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an opaque time-stamp returned by currentTime() into an
|
||||||
|
* elapsed time in milliseconds, based on the current instant in time.
|
||||||
|
*
|
||||||
|
* @param startTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the elapsed time between startTime and now in milliseconds
|
||||||
|
*/
|
||||||
|
static long elapsedMillis(long startTime) {
|
||||||
|
return CLOCK.elapsedMillis0(startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedMillis0(long startTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the difference in milliseconds between two opaque time-stamps returned
|
||||||
|
* by currentTime().
|
||||||
|
*
|
||||||
|
* @param startTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @param endTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the elapsed time between startTime and endTime in milliseconds
|
||||||
|
*/
|
||||||
|
static long elapsedMillis(long startTime, long endTime) {
|
||||||
|
return CLOCK.elapsedMillis0(startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedMillis0(long startTime, long endTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an opaque time-stamp returned by currentTime() into an
|
||||||
|
* elapsed time in milliseconds, based on the current instant in time.
|
||||||
|
*
|
||||||
|
* @param startTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the elapsed time between startTime and now in milliseconds
|
||||||
|
*/
|
||||||
|
static long elapsedNanos(long startTime) {
|
||||||
|
return CLOCK.elapsedNanos0(startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedNanos0(long startTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the difference in nanoseconds between two opaque time-stamps returned
|
||||||
|
* by currentTime().
|
||||||
|
*
|
||||||
|
* @param startTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @param endTime an opaque time-stamp returned by an instance of this class
|
||||||
|
* @return the elapsed time between startTime and endTime in nanoseconds
|
||||||
|
*/
|
||||||
|
static long elapsedNanos(long startTime, long endTime) {
|
||||||
|
return CLOCK.elapsedNanos0(startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
long elapsedNanos0(long startTime, long endTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the specified opaque time-stamp plus the specified number of milliseconds.
|
||||||
|
*
|
||||||
|
* @param time an opaque time-stamp
|
||||||
|
* @param millis milliseconds to add
|
||||||
|
* @return a new opaque time-stamp
|
||||||
|
*/
|
||||||
|
static long plusMillis(long time, long millis) {
|
||||||
|
return CLOCK.plusMillis0(time, millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
long plusMillis0(long time, long millis);
|
||||||
|
|
||||||
|
TimeUnit getSourceTimeUnit0();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a String representation of the elapsed time in appropriate magnitude terminology.
|
||||||
|
*
|
||||||
|
* @param startTime an opaque time-stamp
|
||||||
|
* @param endTime an opaque time-stamp
|
||||||
|
* @return a string representation of the elapsed time interval
|
||||||
|
*/
|
||||||
|
static String elapsedDisplayString(long startTime, long endTime) {
|
||||||
|
return CLOCK.elapsedDisplayString0(startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
default String elapsedDisplayString0(long startTime, long endTime) {
|
||||||
|
long elapsedNanos = elapsedNanos0(startTime, endTime);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(elapsedNanos < 0 ? "-" : "");
|
||||||
|
elapsedNanos = Math.abs(elapsedNanos);
|
||||||
|
|
||||||
|
for (TimeUnit unit : TIMEUNITS_DESCENDING) {
|
||||||
|
long converted = unit.convert(elapsedNanos, TimeUnit.NANOSECONDS);
|
||||||
|
if (converted > 0) {
|
||||||
|
sb.append(converted).append(TIMEUNIT_DISPLAY_VALUES[unit.ordinal()]);
|
||||||
|
elapsedNanos -= TimeUnit.NANOSECONDS.convert(converted, unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeUnit[] TIMEUNITS_DESCENDING = {TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MINUTES,
|
||||||
|
TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS,
|
||||||
|
TimeUnit.NANOSECONDS};
|
||||||
|
|
||||||
|
String[] TIMEUNIT_DISPLAY_VALUES = {"ns", "µs", "ms", "s", "m", "h", "d"};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class used to create a platform-specific ClockSource.
|
||||||
|
*/
|
||||||
|
public class ClockSourceFactory {
|
||||||
|
public static ClockSource create() {
|
||||||
|
return new NanosecondClockSource();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
public class DefaultThreadFactory implements ThreadFactory {
|
||||||
|
|
||||||
|
private final String threadName;
|
||||||
|
|
||||||
|
private final boolean daemon;
|
||||||
|
|
||||||
|
public DefaultThreadFactory(String threadName, boolean daemon) {
|
||||||
|
this.threadName = threadName;
|
||||||
|
this.daemon = daemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable r) {
|
||||||
|
Thread thread = new Thread(r, threadName);
|
||||||
|
thread.setDaemon(daemon);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public class DriverDataSource implements DataSource {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DriverDataSource.class.getName());
|
||||||
|
|
||||||
|
private static final String PASSWORD = "password";
|
||||||
|
|
||||||
|
private static final String USER = "user";
|
||||||
|
|
||||||
|
private String jdbcUrl;
|
||||||
|
|
||||||
|
private final Properties driverProperties;
|
||||||
|
|
||||||
|
private Driver driver;
|
||||||
|
|
||||||
|
public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password) {
|
||||||
|
this.jdbcUrl = jdbcUrl;
|
||||||
|
this.driverProperties = new Properties();
|
||||||
|
for (Entry<Object, Object> entry : properties.entrySet()) {
|
||||||
|
driverProperties.setProperty(entry.getKey().toString(), entry.getValue().toString());
|
||||||
|
}
|
||||||
|
if (username != null) {
|
||||||
|
setUser(username);
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
setPassword(password);
|
||||||
|
}
|
||||||
|
if (driverClassName != null) {
|
||||||
|
Enumeration<Driver> drivers = DriverManager.getDrivers();
|
||||||
|
while (drivers.hasMoreElements()) {
|
||||||
|
Driver d = drivers.nextElement();
|
||||||
|
if (d.getClass().getName().equals(driverClassName)) {
|
||||||
|
driver = d;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (driver == null) {
|
||||||
|
logger.warning("Registered driver with driverClassName was not found, trying direct instantiation: " + driverClassName);
|
||||||
|
Class<?> driverClass = null;
|
||||||
|
ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
try {
|
||||||
|
if (threadContextClassLoader != null) {
|
||||||
|
try {
|
||||||
|
driverClass = threadContextClassLoader.loadClass(driverClassName);
|
||||||
|
logger.fine("Driver class found in Thread context class loader: " + driverClassName + " " + threadContextClassLoader);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.fine("Driver class not found in Thread context class loader, trying classloader: " +
|
||||||
|
driverClassName + " " + threadContextClassLoader + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (driverClass == null) {
|
||||||
|
driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
|
||||||
|
logger.fine("Driver class found in the PoolConfig class classloader:" + driverClassName + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.fine("Failed to load driver class from PoolConfig class classloader: " + driverClassName + " " + this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
if (driverClass != null) {
|
||||||
|
try {
|
||||||
|
driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.WARNING, "Failed to create instance of driver class, trying jdbcUrl resolution: " + driverClassName + " " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String sanitizedUrl = jdbcUrl.replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
|
||||||
|
try {
|
||||||
|
if (driver == null) {
|
||||||
|
driver = DriverManager.getDriver(jdbcUrl);
|
||||||
|
logger.fine("Loaded driver with class name for jdbcUrl: " + driver.getClass().getName() + " " + sanitizedUrl);
|
||||||
|
} else if (!driver.acceptsURL(jdbcUrl)) {
|
||||||
|
throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + sanitizedUrl);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + sanitizedUrl, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return driver.connect(jdbcUrl, driverProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.jdbcUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(String user) {
|
||||||
|
driverProperties.put(USER, driverProperties.getProperty("user", user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
driverProperties.put(PASSWORD, driverProperties.getProperty("password", password));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection(final String username, final String password) throws SQLException {
|
||||||
|
final Properties cloned = (Properties) driverProperties.clone();
|
||||||
|
if (username != null) {
|
||||||
|
cloned.put("user", username);
|
||||||
|
if (cloned.containsKey("username")) {
|
||||||
|
cloned.put("username", username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
cloned.put("password", password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return driver.connect(jdbcUrl, cloned);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLogWriter(PrintWriter logWriter) throws SQLException {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) {
|
||||||
|
DriverManager.setLoginTimeout(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLoginTimeout() {
|
||||||
|
return DriverManager.getLoginTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return driver.getParentLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,357 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.RandomAccess;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast list without range checking.
|
||||||
|
*/
|
||||||
|
public final class FastList<T> implements List<T>, RandomAccess {
|
||||||
|
|
||||||
|
private final Class<?> clazz;
|
||||||
|
|
||||||
|
private T[] elementData;
|
||||||
|
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a FastList with a default size of 32.
|
||||||
|
*
|
||||||
|
* @param clazz the Class stored in the collection
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public FastList(Class<?> clazz) {
|
||||||
|
this.elementData = (T[]) Array.newInstance(clazz, 32);
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a FastList with a specified size.
|
||||||
|
*
|
||||||
|
* @param clazz the Class stored in the collection
|
||||||
|
* @param capacity the initial size of the FastList
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public FastList(Class<?> clazz, int capacity) {
|
||||||
|
this.elementData = (T[]) Array.newInstance(clazz, capacity);
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an element to the tail of the FastList.
|
||||||
|
*
|
||||||
|
* @param element the element to add
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean add(T element) {
|
||||||
|
if (size < elementData.length) {
|
||||||
|
elementData[size++] = element;
|
||||||
|
} else {
|
||||||
|
// overflow-conscious code
|
||||||
|
final int oldCapacity = elementData.length;
|
||||||
|
final int newCapacity = oldCapacity << 1;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
|
||||||
|
System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
|
||||||
|
newElementData[size++] = element;
|
||||||
|
elementData = newElementData;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element at the specified index.
|
||||||
|
*
|
||||||
|
* @param index the index of the element to get
|
||||||
|
* @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T get(int index) {
|
||||||
|
return elementData[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the last element from the list. No bound check is performed, so if this
|
||||||
|
* method is called on an empty list and ArrayIndexOutOfBounds exception will be
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @return the last element of the list
|
||||||
|
*/
|
||||||
|
public T removeLast() {
|
||||||
|
T element = elementData[--size];
|
||||||
|
elementData[size] = null;
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This remove method is most efficient when the element being removed
|
||||||
|
* is the last element. Equality is identity based, not equals() based.
|
||||||
|
* Only the first matching element is removed.
|
||||||
|
*
|
||||||
|
* @param element the element to remove
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object element) {
|
||||||
|
for (int index = size - 1; index >= 0; index--) {
|
||||||
|
if (element == elementData[index]) {
|
||||||
|
final int numMoved = size - index - 1;
|
||||||
|
if (numMoved > 0) {
|
||||||
|
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
|
||||||
|
}
|
||||||
|
elementData[--size] = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the FastList.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
elementData[i] = null;
|
||||||
|
}
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current number of elements in the FastList.
|
||||||
|
*
|
||||||
|
* @return the number of current elements
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T set(int index, T element) {
|
||||||
|
T old = elementData[index];
|
||||||
|
elementData[index] = element;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T remove(int index) {
|
||||||
|
if (size == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final T old = elementData[index];
|
||||||
|
final int numMoved = size - index - 1;
|
||||||
|
if (numMoved > 0) {
|
||||||
|
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
|
||||||
|
}
|
||||||
|
elementData[--size] = null;
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return index < size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (index < size) {
|
||||||
|
return elementData[index++];
|
||||||
|
}
|
||||||
|
throw new NoSuchElementException("No more elements in FastList");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <E> E[] toArray(E[] a) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean containsAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean addAll(int index, Collection<? extends T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void add(int index, T element) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int indexOf(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int lastIndexOf(Object o) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ListIterator<T> listIterator(int index) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<T> subList(int fromIndex, int toIndex) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super T> action) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Spliterator<T> spliterator() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean removeIf(Predicate<? super T> filter) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void replaceAll(UnaryOperator<T> operator) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void sort(Comparator<? super T> c) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.xbib.jdbc.connection.pool.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class NanosecondClockSource implements ClockSource {
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long currentTime0() {
|
||||||
|
return System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long toMillis0(final long time) {
|
||||||
|
return TimeUnit.NANOSECONDS.toMillis(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long toNanos0(final long time) {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long elapsedMillis0(final long startTime) {
|
||||||
|
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long elapsedMillis0(final long startTime, final long endTime) {
|
||||||
|
return TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long elapsedNanos0(final long startTime) {
|
||||||
|
return System.nanoTime() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long elapsedNanos0(final long startTime, final long endTime) {
|
||||||
|
return endTime - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long plusMillis0(final long time, final long millis) {
|
||||||
|
return time + TimeUnit.MILLISECONDS.toNanos(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TimeUnit getSourceTimeUnit0() {
|
||||||
|
return TimeUnit.NANOSECONDS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.Bag;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolEntry;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class BagTest {
|
||||||
|
|
||||||
|
private static PoolDataSource ds;
|
||||||
|
|
||||||
|
private static Pool pool;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
ds = new PoolDataSource(config);
|
||||||
|
pool = ds.getPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void teardown()
|
||||||
|
{
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBag() throws Exception {
|
||||||
|
try (Bag<PoolEntry> bag = new Bag<>((x) -> CompletableFuture.completedFuture(Boolean.TRUE))) {
|
||||||
|
assertEquals(0, bag.values(8).size());
|
||||||
|
PoolEntry reserved = pool.newPoolEntry();
|
||||||
|
bag.add(reserved);
|
||||||
|
bag.reserve(reserved);
|
||||||
|
PoolEntry inuse = pool.newPoolEntry();
|
||||||
|
bag.add(inuse);
|
||||||
|
bag.borrow(2, MILLISECONDS);
|
||||||
|
PoolEntry notinuse = pool.newPoolEntry();
|
||||||
|
bag.add(notinuse);
|
||||||
|
bag.dumpState();
|
||||||
|
bag.requite(reserved);
|
||||||
|
bag.remove(notinuse);
|
||||||
|
assertTrue(bag.getLastMessage().contains("not borrowed or reserved"));
|
||||||
|
bag.unreserve(notinuse);
|
||||||
|
assertTrue(bag.getLastMessage().contains("was not reserved"));
|
||||||
|
bag.remove(inuse);
|
||||||
|
bag.remove(inuse);
|
||||||
|
assertTrue(bag.getLastMessage().contains("not borrowed or reserved"));
|
||||||
|
bag.close();
|
||||||
|
try {
|
||||||
|
PoolEntry bagEntry = pool.newPoolEntry();
|
||||||
|
bag.add(bagEntry);
|
||||||
|
assertNotEquals(bagEntry, bag.borrow(100, MILLISECONDS));
|
||||||
|
}
|
||||||
|
catch (IllegalStateException e) {
|
||||||
|
assertTrue(bag.getLastMessage().contains("ignoring add()"));
|
||||||
|
}
|
||||||
|
assertNotNull(notinuse.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class ConcurrentCloseConnectionTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testConcurrentClose() throws Exception
|
||||||
|
{
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
final Connection connection = ds.getConnection()) {
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(10);
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 500; i++) {
|
||||||
|
final PreparedStatement preparedStatement = connection.prepareStatement("");
|
||||||
|
futures.add(executorService.submit((Callable<Void>) () -> {
|
||||||
|
preparedStatement.close();
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
executorService.shutdown();
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.MockDataSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for cases when db network connectivity goes down and close is called on
|
||||||
|
* existing connections. By default we block longer than getMaximumTimeout
|
||||||
|
* (it can hang for a lot of time depending on driver timeout settings).
|
||||||
|
* Closing the connections asynchronously fixes this issue.
|
||||||
|
*/
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class ConnectionCloseBlockingTest {
|
||||||
|
|
||||||
|
private static volatile boolean shouldFail = false;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
@Test
|
||||||
|
public void testConnectionCloseBlocking() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(1500);
|
||||||
|
config.setDataSource(new CustomMockDataSource());
|
||||||
|
long start = ClockSource.currentTime();
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection connection = ds.getConnection()) {
|
||||||
|
connection.close();
|
||||||
|
PoolTestExtension.quietlySleep(1100L);
|
||||||
|
shouldFail = true;
|
||||||
|
try (Connection connection2 = ds.getConnection()) {
|
||||||
|
assertTrue((ClockSource.elapsedMillis(start) < config.getConnectionTimeout()),
|
||||||
|
"waited longer than timeout: " + config.getConnectionTimeout());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
assertTrue((ClockSource.elapsedMillis(start) < config.getConnectionTimeout()),
|
||||||
|
"getConnection failed because close connection took longer than timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CustomMockDataSource extends MockDataSource {
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
Connection mockConnection = super.getConnection();
|
||||||
|
when(mockConnection.isValid(anyInt())).thenReturn(!shouldFail);
|
||||||
|
doAnswer((Answer<Void>) invocation -> {
|
||||||
|
if (shouldFail) {
|
||||||
|
SECONDS.sleep(2);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).when(mockConnection).close();
|
||||||
|
return mockConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import static org.xbib.jdbc.connection.pool.util.ClockSource.currentTime;
|
||||||
|
import static org.xbib.jdbc.connection.pool.util.ClockSource.elapsedMillis;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubDataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class ConnectionPoolSizeVsThreadsTest {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ConnectionPoolSizeVsThreadsTest.class.getName());
|
||||||
|
|
||||||
|
private static final int ITERATIONS = 50_000;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPoolSizeAboutSameSizeAsThreadCount() throws Exception {
|
||||||
|
final int threadCount = 50;
|
||||||
|
final Counts counts = testPoolSize(2, 100,
|
||||||
|
threadCount, 1, 0, 20,
|
||||||
|
ITERATIONS, TimeUnit.SECONDS.toMillis(2));
|
||||||
|
assertEquals(threadCount, counts.maxActive, 15);
|
||||||
|
assertEquals(threadCount, counts.maxTotal, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSlowConnectionTimeBurstyWork() throws Exception {
|
||||||
|
final int threadCount = 50;
|
||||||
|
final int workItems = threadCount * 100;
|
||||||
|
final int workTimeMs = 0;
|
||||||
|
final int connectionAcquisitionTimeMs = 250;
|
||||||
|
final Counts counts = testPoolSize(2, 100,
|
||||||
|
threadCount, workTimeMs, 0, connectionAcquisitionTimeMs,
|
||||||
|
workItems, TimeUnit.SECONDS.toMillis(3));
|
||||||
|
final long totalWorkTime = workItems * workTimeMs;
|
||||||
|
final long connectionMax = totalWorkTime / connectionAcquisitionTimeMs;
|
||||||
|
assertTrue(connectionMax <= counts.maxActive);
|
||||||
|
assertEquals(connectionMax, counts.maxTotal, 2 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Counts testPoolSize(final int minIdle,
|
||||||
|
final int maxPoolSize,
|
||||||
|
final int threadCount,
|
||||||
|
final long workTimeMs,
|
||||||
|
final long restTimeMs,
|
||||||
|
final long connectionAcquisitionTimeMs,
|
||||||
|
final int iterations,
|
||||||
|
final long postTestTimeMs) throws Exception {
|
||||||
|
LOGGER.info(MessageFormat.format("Starting test (minIdle={0}, maxPoolSize={1}, threadCount={2}, workTimeMs={3}, restTimeMs={4}, connectionAcquisitionTimeMs={5}, iterations={6}, postTestTimeMs={7})",
|
||||||
|
minIdle, maxPoolSize, threadCount, workTimeMs, restTimeMs, connectionAcquisitionTimeMs, iterations, postTestTimeMs));
|
||||||
|
final PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(minIdle);
|
||||||
|
config.setMaximumPoolSize(maxPoolSize);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
final AtomicReference<Exception> ref = new AtomicReference<>(null);
|
||||||
|
try (final PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
|
||||||
|
// connection acquisition takes more than 0 ms in a real system
|
||||||
|
stubDataSource.setConnectionAcquistionTime(connectionAcquisitionTimeMs);
|
||||||
|
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
|
||||||
|
final CountDownLatch allThreadsDone = new CountDownLatch(iterations);
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
threadPool.submit(() -> {
|
||||||
|
if (ref.get() == null) {
|
||||||
|
PoolTestExtension.quietlySleep(restTimeMs);
|
||||||
|
try (Connection c2 = ds.getConnection()) {
|
||||||
|
PoolTestExtension.quietlySleep(workTimeMs);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
ref.set(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allThreadsDone.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
final Pool pool = ds.getPool();
|
||||||
|
final Counts underLoad = new Counts();
|
||||||
|
while (allThreadsDone.getCount() > 0 || pool.getTotalConnections() < minIdle) {
|
||||||
|
PoolTestExtension.quietlySleep(50);
|
||||||
|
underLoad.updateMaxCounts(pool);
|
||||||
|
}
|
||||||
|
LOGGER.info(MessageFormat.format("test over, waiting for post delay time {0} ms ", postTestTimeMs));
|
||||||
|
PoolTestExtension.quietlySleep(connectionAcquisitionTimeMs + workTimeMs + restTimeMs);
|
||||||
|
final Counts postLoad = new Counts();
|
||||||
|
final long start = currentTime();
|
||||||
|
while (elapsedMillis(start) < postTestTimeMs) {
|
||||||
|
PoolTestExtension.quietlySleep(50);
|
||||||
|
postLoad.updateMaxCounts(pool);
|
||||||
|
}
|
||||||
|
allThreadsDone.await();
|
||||||
|
threadPool.shutdown();
|
||||||
|
threadPool.awaitTermination(30, TimeUnit.SECONDS);
|
||||||
|
if (ref.get() != null) {
|
||||||
|
LOGGER.severe("task failed: " + ref.get());
|
||||||
|
fail("task failed");
|
||||||
|
}
|
||||||
|
LOGGER.info("under load... " + underLoad);
|
||||||
|
LOGGER.info("post load.... " + postLoad);
|
||||||
|
if (postTestTimeMs > 0) {
|
||||||
|
if (postLoad.maxActive != 0) {
|
||||||
|
fail("max active was greater than 0 after test was done");
|
||||||
|
}
|
||||||
|
final int createdAfterWorkAllFinished = postLoad.maxTotal - underLoad.maxTotal;
|
||||||
|
assertEquals(0, createdAfterWorkAllFinished, 1,
|
||||||
|
"connections were created when there was no waiting consumers");
|
||||||
|
}
|
||||||
|
return underLoad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Counts {
|
||||||
|
int maxTotal = 0;
|
||||||
|
int maxActive = 0;
|
||||||
|
|
||||||
|
void updateMaxCounts(Pool pool) {
|
||||||
|
maxTotal = Math.max(pool.getTotalConnections(), maxTotal);
|
||||||
|
maxActive = Math.max(pool.getActiveConnections(), maxActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "counts{" + "max total=" + maxTotal + ", max active=" + maxActive + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class ConnectionRaceConditionTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ConnectionRaceConditionTest.class.getName());
|
||||||
|
|
||||||
|
public static final int ITERATIONS = 10_000;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRaceCondition() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTimeout(5000);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
final AtomicReference<Exception> ref = new AtomicReference<>(null);
|
||||||
|
try (final PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ExecutorService threadPool = Executors.newFixedThreadPool(2);
|
||||||
|
for (int i = 0; i < ITERATIONS; i++) {
|
||||||
|
threadPool.submit(new Callable<Exception>() {
|
||||||
|
@Override
|
||||||
|
public Exception call() throws Exception {
|
||||||
|
if (ref.get() == null) {
|
||||||
|
Connection c2;
|
||||||
|
try {
|
||||||
|
c2 = ds.getConnection();
|
||||||
|
ds.evictConnection(c2);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ref.set(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
threadPool.shutdown();
|
||||||
|
threadPool.awaitTermination(30, TimeUnit.SECONDS);
|
||||||
|
if (ref.get() != null) {
|
||||||
|
logger.severe("task failed: " + ref.get());
|
||||||
|
fail("task failed");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.ProxyConnection;
|
||||||
|
|
||||||
|
public class ConnectionStateTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoCommit() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("user", "bar");
|
||||||
|
properties.put("password", "secret");
|
||||||
|
properties.put("url", "baf");
|
||||||
|
properties.put("loginTimeout", "10");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setAutoCommit(true);
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
Connection unwrap = connection.unwrap(Connection.class);
|
||||||
|
unwrap.setAutoCommit(false);
|
||||||
|
connection.close();
|
||||||
|
assertFalse(unwrap.getAutoCommit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionIsolation() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setTransactionIsolation("TRANSACTION_READ_COMMITTED");
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
Connection unwrap = connection.unwrap(Connection.class);
|
||||||
|
unwrap.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
|
||||||
|
connection.close();
|
||||||
|
assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, unwrap.getTransactionIsolation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsolation() {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setTransactionIsolation("TRANSACTION_REPEATABLE_READ");
|
||||||
|
config.validate();
|
||||||
|
int transactionIsolation = PoolTestExtension.getTransactionIsolation(config.getTransactionIsolation());
|
||||||
|
assertSame(Connection.TRANSACTION_REPEATABLE_READ, transactionIsolation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOnly() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setCatalog("test");
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
Connection unwrap = connection.unwrap(Connection.class);
|
||||||
|
connection.setReadOnly(true);
|
||||||
|
connection.close();
|
||||||
|
assertFalse(unwrap.isReadOnly());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCatalog() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setCatalog("test");
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
Connection unwrap = connection.unwrap(Connection.class);
|
||||||
|
connection.setCatalog("other");
|
||||||
|
connection.close();
|
||||||
|
assertEquals("test", unwrap.getCatalog());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommitTracking() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setAutoCommit(false);
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
statement.execute("SELECT something");
|
||||||
|
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
connection.commit();
|
||||||
|
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
statement.execute("SELECT something", Statement.NO_GENERATED_KEYS);
|
||||||
|
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
connection.rollback();
|
||||||
|
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
ResultSet resultSet = statement.executeQuery("SELECT something");
|
||||||
|
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
connection.rollback(null);
|
||||||
|
assertFalse(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
resultSet.updateRow();
|
||||||
|
assertTrue(((ProxyConnection)connection).isCommitStateDirty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,552 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubDataSource;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLTransientConnectionException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolInitializationException;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class ConnectionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setConnectionInitSql("SELECT 1");
|
||||||
|
config.setReadOnly(true);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setLeakDetectionThreshold(TimeUnit.SECONDS.toMillis(30));
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ds.setLoginTimeout(10);
|
||||||
|
assertSame(10, ds.getLoginTimeout());
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
ds.getConnection().close();
|
||||||
|
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT * FROM device WHERE device_id=?")) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
assertNotNull(statement);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "idle connections not as expected");
|
||||||
|
statement.setInt(1, 0);
|
||||||
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
assertNotNull(resultSet);
|
||||||
|
assertFalse(resultSet.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaxLifetime() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(1500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(2000L);
|
||||||
|
config.setMaxLifetime(30000L); // must be 30s or higher
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
Connection unwrap;
|
||||||
|
Connection unwrap2;
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap = connection.unwrap(Connection.class);
|
||||||
|
assertNotNull(connection);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap2 = connection.unwrap(Connection.class);
|
||||||
|
assertSame(unwrap, unwrap2);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
//pool.evictConnection(connection);
|
||||||
|
}
|
||||||
|
Thread.sleep(31000L);
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap2 = connection.unwrap(Connection.class);
|
||||||
|
assertNotSame(unwrap, unwrap2, "Expected a different connection");
|
||||||
|
}
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Post total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Post idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaxLifetime2() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(100L);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ds.getPool().getConfig().setMaxLifetime(700);
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertSame(0, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
Connection unwrap;
|
||||||
|
Connection unwrap2;
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap = connection.unwrap(Connection.class);
|
||||||
|
assertNotNull(connection);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap2 = connection.unwrap(Connection.class);
|
||||||
|
assertSame(unwrap, unwrap2);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertSame( 0, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
PoolTestExtension.quietlySleep(800);
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
unwrap2 = connection.unwrap(Connection.class);
|
||||||
|
assertNotSame(unwrap, unwrap2, "Expected a different connection");
|
||||||
|
}
|
||||||
|
assertSame( 1, pool.getTotalConnections(), "Post total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Post idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleClose() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection connection = ds.getConnection()) {
|
||||||
|
connection.close();
|
||||||
|
connection.abort(null);
|
||||||
|
assertTrue(connection.isClosed(), "Connection should have closed");
|
||||||
|
assertFalse(connection.isValid(5), "Connection should have closed");
|
||||||
|
assertTrue(connection.toString().contains("ProxyConnection"), "Expected to contain ProxyConnection, but was " + connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEviction() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Connection connection = ds.getConnection();
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertEquals(1, pool.getTotalConnections());
|
||||||
|
ds.evictConnection(connection);
|
||||||
|
assertEquals(0, pool.getTotalConnections());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEviction2() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
//config.setExceptionOverrideClassName(OverrideHandler.class.getName());
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
while (pool.getTotalConnections() < 5) {
|
||||||
|
PoolTestExtension.quietlySleep(100L);
|
||||||
|
}
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
|
||||||
|
assertNotNull(statement);
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
assertNotNull(resultSet);
|
||||||
|
try {
|
||||||
|
statement.getMaxFieldSize();
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertSame(SQLException.class, e.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEviction3() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
while (pool.getTotalConnections() < 5) {
|
||||||
|
PoolTestExtension.quietlySleep(100L);
|
||||||
|
}
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
|
||||||
|
assertNotNull(statement);
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
assertNotNull(resultSet);
|
||||||
|
try {
|
||||||
|
statement.getMaxFieldSize();
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertSame(SQLException.class, e.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEvictAllRefill() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(100L);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
final Connection conn = ds.getConnection();
|
||||||
|
ds.evictConnection(conn);
|
||||||
|
}
|
||||||
|
PoolTestExtension.quietlySleep(SECONDS.toMillis(2));
|
||||||
|
int count = 0;
|
||||||
|
while (pool.getIdleConnections() < 5 && count++ < 20) {
|
||||||
|
PoolTestExtension.quietlySleep(100);
|
||||||
|
}
|
||||||
|
assertEquals(5, pool.getIdleConnections(),
|
||||||
|
"after eviction, refill did not reach expected 5 connections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBackfill() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(4);
|
||||||
|
config.setConnectionTimeout(1000);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
PoolTestExtension.quietlySleep(1250);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT some, thing FROM somewhere WHERE something=?");
|
||||||
|
assertNotNull(statement);
|
||||||
|
ResultSet resultSet = statement.executeQuery();
|
||||||
|
assertNotNull(resultSet);
|
||||||
|
try {
|
||||||
|
statement.getMaxFieldSize();
|
||||||
|
fail();
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertSame(SQLException.class, e.getClass());
|
||||||
|
}
|
||||||
|
pool.logPoolState("testBackfill() before close...");
|
||||||
|
}
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
pool.logPoolState("testBackfill() after close...");
|
||||||
|
PoolTestExtension.quietlySleep(1250);
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame( 1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
} finally {
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaximumPoolLimit() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(4);
|
||||||
|
config.setConnectionTimeout(20000);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
final AtomicReference<Exception> ref = new AtomicReference<>();
|
||||||
|
StubConnection.count.set(0); // reset counter
|
||||||
|
try (final PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
final Pool pool = ds.getPool();
|
||||||
|
Thread[] threads = new Thread[20];
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
pool.logPoolState("Before acquire ");
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
pool.logPoolState("After acquire ");
|
||||||
|
PoolTestExtension.quietlySleep(500);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
ref.set(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
pool.logPoolState("before check ");
|
||||||
|
assertNull(ref.get(), (ref.get() != null ? ref.get().toString() : ""));
|
||||||
|
assertSame(4, StubConnection.count.get(), "StubConnection count not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOldDriver() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
StubConnection.oldDriver = true;
|
||||||
|
StubStatement.oldDriver = true;
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
PoolTestExtension.quietlySleep(500);
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
// close
|
||||||
|
}
|
||||||
|
PoolTestExtension.quietlySleep(500);
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
// close
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
StubConnection.oldDriver = false;
|
||||||
|
StubStatement.oldDriver = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitializationFailure1() throws Exception {
|
||||||
|
StubDataSource stubDataSource = new StubDataSource();
|
||||||
|
stubDataSource.setThrowException(new SQLException("Connection refused"));
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSource(stubDataSource);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
fail("Initialization should have failed");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitializationFailure2() throws Exception {
|
||||||
|
StubDataSource stubDataSource = new StubDataSource();
|
||||||
|
stubDataSource.setThrowException(new SQLException("Connection refused"));
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSource(stubDataSource);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection ignored = ds.getConnection()) {
|
||||||
|
fail("Initialization should have failed");
|
||||||
|
} catch (SQLTransientConnectionException e) {
|
||||||
|
// passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidConnectionTestQuery() throws Exception {
|
||||||
|
class BadConnection extends StubConnection {
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
throw new SQLException("Simulated exception in createStatement()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StubDataSource stubDataSource = new StubDataSource() {
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() {
|
||||||
|
return new BadConnection();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2));
|
||||||
|
config.setDataSource(stubDataSource);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
fail("getConnection() should have failed");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
assertSame("Simulated exception in createStatement()", e.getNextException().getMessage());
|
||||||
|
}
|
||||||
|
} catch (PoolInitializationException e) {
|
||||||
|
assertSame("Simulated exception in createStatement()", e.getCause().getMessage());
|
||||||
|
}
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
try (PoolDataSource ignored = new PoolDataSource(config)) {
|
||||||
|
fail("Initialization should have failed");
|
||||||
|
} catch (PoolInitializationException e) {
|
||||||
|
// passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataSourceRaisesErrorWhileInitializationTestQuery() throws Exception {
|
||||||
|
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
|
||||||
|
stubDataSource.setErrorOnConnection(true);
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSource(stubDataSource);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection ignored = ds.getConnection()) {
|
||||||
|
fail("Initialization should have failed");
|
||||||
|
} catch (SQLTransientConnectionException e) {
|
||||||
|
// passed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
@Test
|
||||||
|
public void testDataSourceRaisesErrorAfterInitializationTestQuery() throws Exception {
|
||||||
|
StubDataSourceWithErrorSwitch stubDataSource = new StubDataSourceWithErrorSwitch();
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(3));
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setInitializationFailTimeout(TimeUnit.SECONDS.toMillis(2));
|
||||||
|
config.setDataSource(stubDataSource);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection ignored = ds.getConnection()) {
|
||||||
|
stubDataSource.setErrorOnConnection(true);
|
||||||
|
fail("SQLException should occur!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// request will get timed-out
|
||||||
|
assertTrue(e.getMessage().contains("request timed out"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPopulationSlowAcquisition() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMaximumPoolSize(20);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(1000L);
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ds.getPool().getConfig().setIdleTimeout(3000);
|
||||||
|
SECONDS.sleep(2);
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertSame(1, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(1, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
SECONDS.sleep(20);
|
||||||
|
assertSame(20, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertSame(19, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
assertSame(20, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
SECONDS.sleep(5);
|
||||||
|
assertSame(20, pool.getTotalConnections(), "Third total connections not as expected");
|
||||||
|
assertSame(20, pool.getIdleConnections(), "Third idle connections not as expected");
|
||||||
|
} finally {
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMinimumIdleZero() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(1000L);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection ignored = ds.getConnection()) {
|
||||||
|
// passed
|
||||||
|
} catch (SQLTransientConnectionException sqle) {
|
||||||
|
fail("Failed to obtain connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StubDataSourceWithErrorSwitch extends StubDataSource {
|
||||||
|
|
||||||
|
private boolean errorOnConnection = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() {
|
||||||
|
if (!errorOnConnection) {
|
||||||
|
return new StubConnection();
|
||||||
|
}
|
||||||
|
throw new RuntimeException("bad thing happens on datasource");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorOnConnection(boolean errorOnConnection) {
|
||||||
|
this.errorOnConnection = errorOnConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static class OverrideHandler implements SQLExceptionOverride {
|
||||||
|
@java.lang.Override
|
||||||
|
public Override adjudicate(SQLException sqlException) {
|
||||||
|
return (sqlException.getSQLState().equals("08999")) ? Override.DO_NOT_EVICT : Override.CONTINUE_EVICT;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubDataSource;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class ConnectionTimeoutRetryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionRetries() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2800);
|
||||||
|
config.setValidationTimeout(2800);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
|
||||||
|
stubDataSource.setThrowException(new SQLException("Connection refused"));
|
||||||
|
long start = ClockSource.currentTime();
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
connection.close();
|
||||||
|
fail("Should not have been able to get a connection.");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
long elapsed = ClockSource.elapsedMillis(start);
|
||||||
|
long timeout = config.getConnectionTimeout();
|
||||||
|
assertTrue(elapsed >= timeout, "Didn't wait long enough for timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionRetries2() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(2800);
|
||||||
|
config.setValidationTimeout(2800);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
final StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
|
||||||
|
stubDataSource.setThrowException(new SQLException("Connection refused"));
|
||||||
|
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
scheduler.schedule(() -> stubDataSource.setThrowException(null), 300, TimeUnit.MILLISECONDS);
|
||||||
|
long start = ClockSource.currentTime();
|
||||||
|
try {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
// close immediately
|
||||||
|
}
|
||||||
|
long elapsed = ClockSource.elapsedMillis(start);
|
||||||
|
assertTrue(elapsed < config.getConnectionTimeout(), "waited too long to get a connection");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fail("Should not have timed out: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionRetries3() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTimeout(2800);
|
||||||
|
config.setValidationTimeout(2800);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
final Connection connection1 = ds.getConnection();
|
||||||
|
final Connection connection2 = ds.getConnection();
|
||||||
|
assertNotNull(connection1);
|
||||||
|
assertNotNull(connection2);
|
||||||
|
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
connection1.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}, 800, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
long start = ClockSource.currentTime();
|
||||||
|
try {
|
||||||
|
try (Connection connection3 = ds.getConnection()) {
|
||||||
|
// close immediately
|
||||||
|
}
|
||||||
|
long elapsed = ClockSource.elapsedMillis(start);
|
||||||
|
assertTrue((elapsed >= 700) && (elapsed < 950), "waited too long to get a connection");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fail("Should not have timed out.");
|
||||||
|
} finally {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionRetries5() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTimeout(1000);
|
||||||
|
config.setValidationTimeout(1000);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
final Connection connection1 = ds.getConnection();
|
||||||
|
long start = ClockSource.currentTime();
|
||||||
|
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||||
|
scheduler.schedule(() -> {
|
||||||
|
try {
|
||||||
|
connection1.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}, 250, TimeUnit.MILLISECONDS);
|
||||||
|
StubDataSource stubDataSource = ds.unwrap(StubDataSource.class);
|
||||||
|
stubDataSource.setThrowException(new SQLException("Connection refused"));
|
||||||
|
try {
|
||||||
|
try (Connection connection2 = ds.getConnection()) {
|
||||||
|
// close immediately
|
||||||
|
}
|
||||||
|
long elapsed = ClockSource.elapsedMillis(start);
|
||||||
|
assertTrue((elapsed >= 250) && (elapsed < config.getConnectionTimeout()), "Waited too long to get a connection");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fail("Should not have timed out.");
|
||||||
|
} finally {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionIdleFill() throws Exception {
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setConnectionTimeout(2000);
|
||||||
|
config.setValidationTimeout(2000);
|
||||||
|
config.setConnectionTestQuery("VALUES 2");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(400);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
try (Connection connection1 = ds.getConnection();
|
||||||
|
Connection connection2 = ds.getConnection();
|
||||||
|
Connection connection3 = ds.getConnection();
|
||||||
|
Connection connection4 = ds.getConnection();
|
||||||
|
Connection connection5 = ds.getConnection();
|
||||||
|
Connection connection6 = ds.getConnection();
|
||||||
|
Connection connection7 = ds.getConnection()) {
|
||||||
|
Thread.sleep(1300);
|
||||||
|
assertSame(10, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(3, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
}
|
||||||
|
assertSame(10, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertSame(10, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.DefaultThreadFactory;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class HouseKeeperCleanupTest {
|
||||||
|
|
||||||
|
private ScheduledThreadPoolExecutor executor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void before() throws Exception {
|
||||||
|
ThreadFactory threadFactory = new DefaultThreadFactory("global-housekeeper", true);
|
||||||
|
executor = new ScheduledThreadPoolExecutor(1, threadFactory,
|
||||||
|
new ThreadPoolExecutor.DiscardPolicy());
|
||||||
|
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||||
|
executor.setRemoveOnCancelPolicy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHouseKeeperCleanupWithCustomExecutor() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTimeout(2500);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setScheduledExecutor(executor);
|
||||||
|
PoolConfig config2 = new PoolConfig();
|
||||||
|
config2.setMinimumIdle(0);
|
||||||
|
config2.setMaximumPoolSize(10);
|
||||||
|
config2.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config2.setConnectionTimeout(2500);
|
||||||
|
config2.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config2.setScheduledExecutor(executor);
|
||||||
|
try (
|
||||||
|
final PoolDataSource ds1 = new PoolDataSource(config);
|
||||||
|
final PoolDataSource ds2 = new PoolDataSource(config2)) {
|
||||||
|
assertEquals(4, executor.getQueue().size(), "scheduled tasks count not as expected");
|
||||||
|
}
|
||||||
|
assertEquals( 0, executor.getQueue().size(), "scheduled tasks count not as expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void after() throws Exception {
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class IsolationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsolation() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setIsolateInternalQueries(true);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
connection.close();
|
||||||
|
try (Connection connection2 = ds.getConnection()) {
|
||||||
|
connection2.close();
|
||||||
|
assertNotSame(connection, connection2);
|
||||||
|
assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNonIsolation() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setIsolateInternalQueries(false);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
connection.close();
|
||||||
|
try (Connection connection2 = ds.getConnection()) {
|
||||||
|
connection2.close();
|
||||||
|
assertSame(connection.unwrap(Connection.class), connection2.unwrap(Connection.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.DriverDataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Properties;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class JdbcDriverTest {
|
||||||
|
|
||||||
|
private PoolDataSource ds;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void teardown() {
|
||||||
|
if (ds != null) {
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void driverTest1() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("url", "jdbc:stub");
|
||||||
|
properties.put("user", "bart");
|
||||||
|
properties.put("password", "simpson");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDriverClassName("org.xbib.io.pool.jdbc.mock.StubDriver");
|
||||||
|
ds = new PoolDataSource(config);
|
||||||
|
assertTrue(ds.isWrapperFor(DriverDataSource.class));
|
||||||
|
DriverDataSource unwrap = ds.unwrap(DriverDataSource.class);
|
||||||
|
assertNotNull(unwrap);
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
// test that getConnection() succeeds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void driverTest2() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("url", "jdbc:invalid");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDriverClassName("org.xbib.io.pool.jdbc.mock.StubDriver");
|
||||||
|
try {
|
||||||
|
ds = new PoolDataSource(config);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
assertTrue(e.getMessage().contains("claims to not accept"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class PoolTest {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PoolTest.class.getName());
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setup() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTestQuery("SELECT 1");
|
||||||
|
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config);
|
||||||
|
Connection conn = ds.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.executeUpdate("DROP TABLE IF EXISTS basic_pool_test");
|
||||||
|
stmt.executeUpdate("CREATE TABLE basic_pool_test ("
|
||||||
|
+ "id INTEGER NOT NULL IDENTITY PRIMARY KEY, "
|
||||||
|
+ "timestamp TIMESTAMP, "
|
||||||
|
+ "string VARCHAR(128), "
|
||||||
|
+ "string_from_number NUMERIC "
|
||||||
|
+ ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdleTimeout() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setConnectionTestQuery("SELECT 1");
|
||||||
|
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
|
||||||
|
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
System.clearProperty("pool.jdbc.housekeeping.periodMs");
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
config.setIdleTimeout(3000);
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
logger.log(Level.INFO, "got connection " + connection);
|
||||||
|
assertNotNull(connection);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(1500);
|
||||||
|
//assertEquals(6, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
//assertEquals(5, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertEquals(4, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
//assertEquals(6, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
assertEquals(5, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
TimeUnit.SECONDS.sleep(2);
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "Third total connections not as expected");
|
||||||
|
assertEquals(5, pool.getIdleConnections(), "Third idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdleTimeout2() throws Exception {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
properties.put("url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
|
||||||
|
PoolConfig config = new PoolConfig(properties);
|
||||||
|
config.setMaximumPoolSize(50);
|
||||||
|
config.setConnectionTestQuery("SELECT 1");
|
||||||
|
config.setDataSourceClassName("org.h2.jdbcx.JdbcDataSource");
|
||||||
|
System.setProperty("pool.jdbc.housekeeping.periodMs", "1000");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
System.clearProperty("pool.jdbc.housekeeping.periodMs");
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
config.setIdleTimeout(3000);
|
||||||
|
assertEquals(50, pool.getTotalConnections(), "Total connections not as expected");
|
||||||
|
assertEquals(50, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(1500);
|
||||||
|
assertEquals(50, pool.getTotalConnections(), "Second total connections not as expected");
|
||||||
|
assertEquals(49, pool.getIdleConnections(), "Second idle connections not as expected");
|
||||||
|
}
|
||||||
|
assertEquals(50, pool.getIdleConnections(), "Idle connections not as expected");
|
||||||
|
TimeUnit.SECONDS.sleep(3);
|
||||||
|
assertEquals(50, pool.getTotalConnections(), "Third total connections not as expected");
|
||||||
|
assertEquals(50, pool.getIdleConnections(), "Third idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.BeforeAllCallback;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.logging.ConsoleHandler;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.logging.SimpleFormatter;
|
||||||
|
import org.xbib.jdbc.connection.pool.IsolationLevel;
|
||||||
|
|
||||||
|
public class PoolTestExtension implements BeforeAllCallback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeAll(ExtensionContext context) {
|
||||||
|
//Level level = Level.INFO;
|
||||||
|
Level level = Level.ALL;
|
||||||
|
System.setProperty("java.util.logging.SimpleFormatter.format",
|
||||||
|
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n");
|
||||||
|
LogManager.getLogManager().reset();
|
||||||
|
Logger rootLogger = LogManager.getLogManager().getLogger("");
|
||||||
|
Handler handler = new ConsoleHandler();
|
||||||
|
handler.setFormatter(new SimpleFormatter());
|
||||||
|
rootLogger.addHandler(handler);
|
||||||
|
rootLogger.setLevel(level);
|
||||||
|
for (Handler h : rootLogger.getHandlers()) {
|
||||||
|
handler.setFormatter(new SimpleFormatter());
|
||||||
|
h.setLevel(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void quietlySleep(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the int value of a transaction isolation level by name.
|
||||||
|
*
|
||||||
|
* @param transactionIsolationName the name of the transaction isolation level
|
||||||
|
* @return the int value of the isolation level or -1
|
||||||
|
*/
|
||||||
|
public static int getTransactionIsolation(final String transactionIsolationName) {
|
||||||
|
if (transactionIsolationName != null) {
|
||||||
|
try {
|
||||||
|
final String upperCaseIsolationLevelName = transactionIsolationName.toUpperCase(Locale.ENGLISH);
|
||||||
|
return IsolationLevel.valueOf(upperCaseIsolationLevelName).getLevelId();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
try {
|
||||||
|
final int level = Integer.parseInt(transactionIsolationName);
|
||||||
|
for (IsolationLevel iso : IsolationLevel.values()) {
|
||||||
|
if (iso.getLevelId() == level) {
|
||||||
|
return iso.getLevelId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException nfe) {
|
||||||
|
throw new IllegalArgumentException("Invalid transaction isolation value: " + transactionIsolationName, nfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class ProxiesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProxyCreation() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Connection conn = ds.getConnection();
|
||||||
|
assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
|
||||||
|
assertNotNull(conn.createStatement(ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
|
||||||
|
assertNotNull(conn.prepareCall("some sql"));
|
||||||
|
assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
|
||||||
|
assertNotNull(conn.prepareCall("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
|
||||||
|
assertNotNull(conn.prepareStatement("some sql", PreparedStatement.NO_GENERATED_KEYS));
|
||||||
|
assertNotNull(conn.prepareStatement("some sql", new int[3]));
|
||||||
|
assertNotNull(conn.prepareStatement("some sql", new String[3]));
|
||||||
|
assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE));
|
||||||
|
assertNotNull(conn.prepareStatement("some sql", ResultSet.FETCH_FORWARD, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.HOLD_CURSORS_OVER_COMMIT));
|
||||||
|
assertNotNull(conn.toString());
|
||||||
|
assertTrue(conn.isWrapperFor(Connection.class));
|
||||||
|
assertTrue(conn.isValid(10));
|
||||||
|
assertFalse(conn.isClosed());
|
||||||
|
assertNotNull(conn.unwrap(StubConnection.class));
|
||||||
|
try {
|
||||||
|
conn.unwrap(ProxiesTest.class);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatementProxy() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Connection conn = ds.getConnection();
|
||||||
|
PreparedStatement stmt = conn.prepareStatement("some sql");
|
||||||
|
stmt.executeQuery();
|
||||||
|
stmt.executeQuery("some sql");
|
||||||
|
assertFalse(stmt.isClosed());
|
||||||
|
assertNotNull(stmt.getGeneratedKeys());
|
||||||
|
assertNotNull(stmt.getResultSet());
|
||||||
|
assertNotNull(stmt.getConnection());
|
||||||
|
assertNotNull(stmt.unwrap(StubStatement.class));
|
||||||
|
try {
|
||||||
|
stmt.unwrap(ProxiesTest.class);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatementExceptions() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTimeout(TimeUnit.SECONDS.toMillis(1));
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Connection conn = ds.getConnection();
|
||||||
|
StubConnection stubConnection = conn.unwrap(StubConnection.class);
|
||||||
|
stubConnection.throwException = true;
|
||||||
|
try {
|
||||||
|
conn.createStatement();
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.createStatement(0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.createStatement(0, 0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareCall("");
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareCall("", 0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareCall("", 0, 0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("");
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("", 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("", new int[0]);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("", new String[0]);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("", 0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.prepareStatement("", 0, 0, 0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOtherExceptions() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
try (Connection conn = ds.getConnection()) {
|
||||||
|
StubConnection stubConnection = conn.unwrap(StubConnection.class);
|
||||||
|
stubConnection.throwException = true;
|
||||||
|
try {
|
||||||
|
conn.setTransactionIsolation(Connection.TRANSACTION_NONE);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.isReadOnly();
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.setReadOnly(false);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.setCatalog("");
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.clearWarnings();
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.isValid(0);
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.isWrapperFor(getClass());
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.unwrap(getClass());
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
fail();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
assertFalse(conn.isValid(0));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class RampUpDownTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rampUpDownTest() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(60);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setIdleTimeout(1000);
|
||||||
|
config.setHousekeepingPeriodMs(250L);
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertSame(1, pool.getTotalConnections(), "total connections not as expected");
|
||||||
|
PoolTestExtension.quietlySleep(500);
|
||||||
|
Connection[] connections = new Connection[ds.getPool().getConfig().getMaximumPoolSize()];
|
||||||
|
for (int i = 0; i < connections.length; i++) {
|
||||||
|
connections[i] = ds.getConnection();
|
||||||
|
}
|
||||||
|
assertSame(60, pool.getTotalConnections(), "total connections not as expected");
|
||||||
|
for (Connection connection : connections) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
PoolTestExtension.quietlySleep(500);
|
||||||
|
assertSame(60, pool.getIdleConnections(), "idle connections not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubStatement;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class SaturatedPoolTest830 {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(SaturatedPoolTest830.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_POOL_SIZE = 10;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void saturatedPoolTest() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(MAX_POOL_SIZE);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTimeout(1000);
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
config.setHousekeepingPeriodMs(5000L);
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
StubStatement.setSimulatedQueryTime(1000);
|
||||||
|
final long start = ClockSource.currentTime();
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||||
|
ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 50, 50,
|
||||||
|
2, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
threadPool.allowCoreThreadTimeOut(true);
|
||||||
|
AtomicInteger windowIndex = new AtomicInteger();
|
||||||
|
boolean[] failureWindow = new boolean[100];
|
||||||
|
Arrays.fill(failureWindow, true);
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
try (Connection conn = ds.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute("SELECT bogus FROM imaginary");
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
logger.info(e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
long sleep = 80;
|
||||||
|
outer: while (true) {
|
||||||
|
PoolTestExtension.quietlySleep(sleep);
|
||||||
|
if (ClockSource.elapsedMillis(start) > TimeUnit.SECONDS.toMillis(12) && sleep < 100) {
|
||||||
|
sleep = 100;
|
||||||
|
logger.warning("switching to 100ms sleep");
|
||||||
|
}
|
||||||
|
else if (ClockSource.elapsedMillis(start) > TimeUnit.SECONDS.toMillis(6) && sleep < 90) {
|
||||||
|
sleep = 90;
|
||||||
|
logger.warning("switching to 90ms sleep");
|
||||||
|
}
|
||||||
|
threadPool.execute(() -> {
|
||||||
|
int ndx = windowIndex.incrementAndGet() % failureWindow.length;
|
||||||
|
try (Connection conn = ds.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute("SELECT bogus FROM imaginary");
|
||||||
|
failureWindow[ndx] = false;
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
logger.info(e.getMessage());
|
||||||
|
failureWindow[ndx] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (boolean b : failureWindow) {
|
||||||
|
if (b) {
|
||||||
|
if (ClockSource.elapsedMillis(start) % (TimeUnit.SECONDS.toMillis(1) - sleep) < sleep) {
|
||||||
|
logger.info(MessageFormat.format("active threads {0}, submissions per second {1}, waiting threads {2}",
|
||||||
|
threadPool.getActiveCount(),
|
||||||
|
TimeUnit.SECONDS.toMillis(1) / sleep,
|
||||||
|
ds.getPool().getThreadsAwaitingConnection()));
|
||||||
|
}
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info(MessageFormat.format("active threads {0}, submissions per second {1}, waiting threads {2}",
|
||||||
|
threadPool.getActiveCount(),
|
||||||
|
TimeUnit.SECONDS.toMillis(1) / sleep,
|
||||||
|
ds.getPool().getThreadsAwaitingConnection()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
logger.info("waiting for completion of active tasks: " + threadPool.getActiveCount());
|
||||||
|
while (ds.getPool().getActiveConnections() > 0) {
|
||||||
|
PoolTestExtension.quietlySleep(50);
|
||||||
|
}
|
||||||
|
assertEquals(TimeUnit.SECONDS.toMillis(1) / sleep, 10L, "Rate not in balance at 10req/s");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
StubStatement.setSimulatedQueryTime(0);
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,262 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.jdbc.connection.pool.util.ClockSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
@ExtendWith(PoolTestExtension.class)
|
||||||
|
public class ShutdownTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void beforeTest() {
|
||||||
|
StubConnection.count.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void afterTest() {
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdown1() throws Exception {
|
||||||
|
assertSame(0, StubConnection.count.get(), "StubConnection count not as expected");
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
Thread[] threads = new Thread[10];
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
if (ds.getConnection() != null) {
|
||||||
|
PoolTestExtension.quietlySleep(TimeUnit.SECONDS.toMillis(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
});
|
||||||
|
threads[i].setDaemon(true);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
PoolTestExtension.quietlySleep(1800L);
|
||||||
|
assertTrue(pool.getTotalConnections() > 0, "total connection count not as expected");
|
||||||
|
ds.close();
|
||||||
|
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
|
||||||
|
assertSame( 0, pool.getIdleConnections(), "idle connection count not as expected");
|
||||||
|
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
|
||||||
|
assertTrue(ds.isClosed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdown2() throws Exception {
|
||||||
|
assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected");
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(10);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
PoolTestExtension.quietlySleep(1200L);
|
||||||
|
assertTrue( pool.getTotalConnections() > 0, "total connection count not as expected");
|
||||||
|
ds.close();
|
||||||
|
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "idle connection count not as expected");
|
||||||
|
assertSame(0, pool.getTotalConnections(), "Total connection count not as expected");
|
||||||
|
assertTrue(ds.toString().startsWith("PoolDataSource (") && ds.toString().endsWith(")"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdown3() throws Exception {
|
||||||
|
assertSame(0, StubConnection.count.get(), "StubConnection count not as expected");
|
||||||
|
StubConnection.slowCreate = false;
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
PoolTestExtension.quietlySleep(1200L);
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "total connection count not as expected");
|
||||||
|
ds.close();
|
||||||
|
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
|
||||||
|
assertSame( 0, pool.getIdleConnections(), "idle connection count not as expected");
|
||||||
|
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdown4() throws Exception {
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(10);
|
||||||
|
config.setMaximumPoolSize(10);
|
||||||
|
config.setInitializationFailTimeout(Long.MAX_VALUE);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
PoolTestExtension.quietlySleep(500L);
|
||||||
|
ds.close();
|
||||||
|
long startTime = ClockSource.currentTime();
|
||||||
|
while (ClockSource.elapsedMillis(startTime) < TimeUnit.SECONDS.toMillis(5) && threadCount() > 0) {
|
||||||
|
PoolTestExtension.quietlySleep(250);
|
||||||
|
}
|
||||||
|
assertSame(0, ds.getPool().getTotalConnections(), "unreleased connections after shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdown5() throws Exception {
|
||||||
|
assertSame( 0, StubConnection.count.get(), "StubConnection count not as expected");
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
ds.getConnection();
|
||||||
|
}
|
||||||
|
assertEquals(5, pool.getTotalConnections(), "total connection count not as expected, ");
|
||||||
|
ds.close();
|
||||||
|
assertSame(0, pool.getActiveConnections(), "active connection count not as expected");
|
||||||
|
assertSame(0, pool.getIdleConnections(), "idle connection count not as expected");
|
||||||
|
assertSame(0, pool.getTotalConnections(), "total connection count not as expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAfterShutdown() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(0);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ds.close();
|
||||||
|
try {
|
||||||
|
ds.getConnection();
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
assertTrue(e.getMessage().contains("has been closed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShutdownDuringInit() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(1000);
|
||||||
|
config.setValidationTimeout(1000);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
StubConnection.slowCreate = true;
|
||||||
|
PoolTestExtension.quietlySleep(3000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThreadedShutdown() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(5);
|
||||||
|
config.setMaximumPoolSize(5);
|
||||||
|
config.setConnectionTimeout(1000);
|
||||||
|
config.setValidationTimeout(1000);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
for (int i1 = 0; i1 < 10; i1++) {
|
||||||
|
Connection connection2 = null;
|
||||||
|
try {
|
||||||
|
connection2 = ds.getConnection();
|
||||||
|
PreparedStatement stmt = connection2.prepareStatement("SOMETHING");
|
||||||
|
PoolTestExtension.quietlySleep(20);
|
||||||
|
stmt.getMaxFieldSize();
|
||||||
|
}
|
||||||
|
catch (SQLException e) {
|
||||||
|
try {
|
||||||
|
if (connection2 != null) {
|
||||||
|
connection2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException e2) {
|
||||||
|
if (e2.getMessage().contains("shutdown") || e2.getMessage().contains("evicted")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
Thread t2 = new Thread(() -> {
|
||||||
|
PoolTestExtension.quietlySleep(100);
|
||||||
|
try {
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
catch (IllegalStateException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t2.start();
|
||||||
|
t.join();
|
||||||
|
t2.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int threadCount() {
|
||||||
|
Thread[] threads = new Thread[Thread.activeCount() * 2];
|
||||||
|
Thread.enumerate(threads);
|
||||||
|
int count = 0;
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
count += (thread != null && thread.getName().startsWith("Pool")) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import org.xbib.jdbc.connection.pool.Pool;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class StatementTest {
|
||||||
|
|
||||||
|
private PoolDataSource ds;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(2);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
ds = new PoolDataSource(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void teardown() {
|
||||||
|
ds.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatementClose() throws SQLException {
|
||||||
|
ds.getConnection().close();
|
||||||
|
Pool pool = ds.getPool();
|
||||||
|
assertTrue(pool.getTotalConnections() >= 1, "total connections not as expected");
|
||||||
|
assertTrue(pool.getIdleConnections() >= 1, "idle connections not as expected");
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
assertTrue(pool.getTotalConnections() >= 1, "total connections not as expected");
|
||||||
|
assertTrue(pool.getIdleConnections() >= 0, "idle connections not as expected");
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
assertNotNull(statement);
|
||||||
|
connection.close();
|
||||||
|
assertTrue(statement.isClosed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoStatementClose() throws SQLException {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
Statement statement1 = connection.createStatement();
|
||||||
|
assertNotNull(statement1);
|
||||||
|
Statement statement2 = connection.createStatement();
|
||||||
|
assertNotNull(statement2);
|
||||||
|
connection.close();
|
||||||
|
assertTrue(statement1.isClosed());
|
||||||
|
assertTrue(statement2.isClosed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatementResultSetProxyClose() throws SQLException {
|
||||||
|
try (Connection connection = ds.getConnection()) {
|
||||||
|
assertNotNull(connection);
|
||||||
|
Statement statement1 = connection.createStatement();
|
||||||
|
assertNotNull(statement1);
|
||||||
|
Statement statement2 = connection.createStatement();
|
||||||
|
assertNotNull(statement2);
|
||||||
|
statement1.getResultSet().getStatement().close();
|
||||||
|
statement2.getGeneratedKeys().getStatement().close();
|
||||||
|
assertTrue(statement1.isClosed());
|
||||||
|
assertTrue(statement2.isClosed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoubleStatementClose() throws SQLException {
|
||||||
|
try (Connection connection = ds.getConnection();
|
||||||
|
Statement statement1 = connection.createStatement()) {
|
||||||
|
statement1.close();
|
||||||
|
statement1.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOutOfOrderStatementClose() throws SQLException {
|
||||||
|
try (Connection connection = ds.getConnection();
|
||||||
|
Statement statement1 = connection.createStatement();
|
||||||
|
Statement statement2 = connection.createStatement()) {
|
||||||
|
statement1.close();
|
||||||
|
statement2.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.xbib.io.pool.jdbc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubConnection;
|
||||||
|
import org.xbib.io.pool.jdbc.mock.StubDataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
|
public class UnwrapTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwrapConnection() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
ds.getConnection().close();
|
||||||
|
assertSame(1, ds.getPool().getIdleConnections(), "Idle connections not as expected");
|
||||||
|
Connection connection = ds.getConnection();
|
||||||
|
assertNotNull(connection);
|
||||||
|
StubConnection unwrapped = connection.unwrap(StubConnection.class);
|
||||||
|
assertNotNull(unwrapped, "unwrapped connection is not instance of StubConnection: " + unwrapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnwrapDataSource() throws Exception {
|
||||||
|
PoolConfig config = new PoolConfig();
|
||||||
|
config.setMinimumIdle(1);
|
||||||
|
config.setMaximumPoolSize(1);
|
||||||
|
config.setInitializationFailTimeout(0);
|
||||||
|
config.setConnectionTestQuery("VALUES 1");
|
||||||
|
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
|
||||||
|
try (PoolDataSource ds = new PoolDataSource(config)) {
|
||||||
|
StubDataSource unwrap = ds.unwrap(StubDataSource.class);
|
||||||
|
assertNotNull(unwrap);
|
||||||
|
assertTrue(ds.isWrapperFor(PoolDataSource.class));
|
||||||
|
assertNotNull(ds.unwrap(PoolDataSource.class));
|
||||||
|
assertFalse(ds.isWrapperFor(getClass()));
|
||||||
|
try {
|
||||||
|
ds.unwrap(getClass());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Logger.getAnonymousLogger().log(Level.INFO, e.getMessage());
|
||||||
|
assertTrue(e.getMessage().contains("wrapped DataSource"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public class MockDataSource implements DataSource {
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return createMockConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection getConnection(String username, String password) throws SQLException {
|
||||||
|
return getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLoginTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Connection createMockConnection() throws SQLException {
|
||||||
|
final Connection mockConnection = mock(Connection.class);
|
||||||
|
when(mockConnection.getAutoCommit()).thenReturn(true);
|
||||||
|
Statement statement = mock(Statement.class);
|
||||||
|
when(mockConnection.createStatement()).thenReturn(statement);
|
||||||
|
when(mockConnection.createStatement(anyInt(), anyInt())).thenReturn(statement);
|
||||||
|
when(mockConnection.createStatement(anyInt(), anyInt(), anyInt())).thenReturn(statement);
|
||||||
|
when(mockConnection.isValid(anyInt())).thenReturn(true);
|
||||||
|
PreparedStatement mockPreparedStatement = mock(PreparedStatement.class);
|
||||||
|
when(mockConnection.prepareStatement(anyString())).thenReturn(mockPreparedStatement);
|
||||||
|
when(mockConnection.prepareStatement(anyString(), anyInt())).thenReturn(mockPreparedStatement);
|
||||||
|
when(mockConnection.prepareStatement(anyString(), any(int[].class))).thenReturn(mockPreparedStatement);
|
||||||
|
when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(mockPreparedStatement);
|
||||||
|
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
|
||||||
|
when(mockConnection.prepareStatement(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockPreparedStatement);
|
||||||
|
doAnswer(new Answer<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).doNothing().when(mockPreparedStatement).setInt(anyInt(), anyInt());
|
||||||
|
ResultSet mockResultSet = mock(ResultSet.class);
|
||||||
|
when(mockPreparedStatement.executeQuery()).thenReturn(mockResultSet);
|
||||||
|
when(mockResultSet.getString(anyInt())).thenReturn("aString");
|
||||||
|
when(mockResultSet.next()).thenReturn(true);
|
||||||
|
CallableStatement mockCallableStatement = mock(CallableStatement.class);
|
||||||
|
when(mockConnection.prepareCall(anyString())).thenReturn(mockCallableStatement);
|
||||||
|
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
|
||||||
|
when(mockConnection.prepareCall(anyString(), anyInt(), anyInt(), anyInt())).thenReturn(mockCallableStatement);
|
||||||
|
return mockConnection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
public abstract class StubBaseConnection implements Connection {
|
||||||
|
|
||||||
|
public volatile boolean throwException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement() throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,522 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import org.xbib.io.pool.jdbc.PoolTestExtension;
|
||||||
|
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.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.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class StubConnection extends StubBaseConnection implements Connection {
|
||||||
|
|
||||||
|
public static final AtomicInteger count = new AtomicInteger();
|
||||||
|
|
||||||
|
public static volatile boolean slowCreate;
|
||||||
|
|
||||||
|
public static volatile boolean oldDriver;
|
||||||
|
|
||||||
|
private static final long foo;
|
||||||
|
|
||||||
|
private boolean autoCommit;
|
||||||
|
|
||||||
|
private int isolation = Connection.TRANSACTION_READ_COMMITTED;
|
||||||
|
|
||||||
|
private String catalog;
|
||||||
|
|
||||||
|
static {
|
||||||
|
foo = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StubConnection() {
|
||||||
|
count.incrementAndGet();
|
||||||
|
if (slowCreate) {
|
||||||
|
PoolTestExtension.quietlySleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iface.isInstance(this)) {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SQLException("Wrapped connection is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String nativeSQL(String sql) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAutoCommit(boolean autoCommit) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
this.autoCommit = autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getAutoCommit() throws SQLException {
|
||||||
|
return autoCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void commit() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void rollback() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DatabaseMetaData getMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setReadOnly(boolean readOnly) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCatalog(String catalog) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
this.catalog = catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getCatalog() throws SQLException {
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTransactionIsolation(int level) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
this.isolation = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTransactionIsolation() throws SQLException {
|
||||||
|
return isolation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Class<?>> getTypeMap() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setHoldability(int holdability) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getHoldability() throws SQLException {
|
||||||
|
return (int) foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Savepoint setSavepoint(String name) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void rollback(Savepoint savepoint) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return new StubPreparedStatement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Clob createClob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Blob createBlob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public NClob createNClob() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SQLXML createSQLXML() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValid(int timeout) throws SQLException {
|
||||||
|
if (throwException) {
|
||||||
|
throw new SQLException();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(String name, String value) throws SQLClientInfoException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setClientInfo(Properties properties) throws SQLClientInfoException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getClientInfo(String name) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Properties getClientInfo() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public void setSchema(String schema) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public String getSchema() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public void abort(Executor executor) throws SQLException {
|
||||||
|
throw new SQLException("Intentional exception during abort");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public int getNetworkTimeout() throws SQLException {
|
||||||
|
if (oldDriver) {
|
||||||
|
throw new AbstractMethodError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import org.xbib.io.pool.jdbc.PoolTestExtension;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
public class StubDataSource implements DataSource {
|
||||||
|
private String user;
|
||||||
|
private String password;
|
||||||
|
private PrintWriter logWriter;
|
||||||
|
private SQLException throwException;
|
||||||
|
private long connectionAcquistionTime = 0;
|
||||||
|
private int loginTimeout;
|
||||||
|
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(String user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setURL(String url) {
|
||||||
|
// we don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PrintWriter getLogWriter() throws SQLException {
|
||||||
|
return logWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLogWriter(PrintWriter out) throws SQLException {
|
||||||
|
this.logWriter = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLoginTimeout(int seconds) throws SQLException {
|
||||||
|
this.loginTimeout = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getLoginTimeout() throws SQLException {
|
||||||
|
return loginTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(this)) {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
throw new SQLException("wrapped DataSource is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
if (throwException != null) {
|
||||||
|
throw throwException;
|
||||||
|
}
|
||||||
|
if (connectionAcquistionTime > 0) {
|
||||||
|
PoolTestExtension.quietlySleep(connectionAcquistionTime);
|
||||||
|
}
|
||||||
|
return new StubConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection(String username, String password) throws SQLException {
|
||||||
|
return new StubConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThrowException(SQLException e) {
|
||||||
|
this.throwException = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionAcquistionTime(long connectionAcquisitionTime) {
|
||||||
|
this.connectionAcquistionTime = connectionAcquisitionTime;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.DriverPropertyInfo;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class StubDriver implements Driver {
|
||||||
|
private static final Driver driver;
|
||||||
|
|
||||||
|
static {
|
||||||
|
driver = new StubDriver();
|
||||||
|
try {
|
||||||
|
DriverManager.registerDriver(driver);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
return new StubConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean acceptsURL(String url) throws SQLException {
|
||||||
|
return "jdbc:stub".equals(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMajorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMinorVersion() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean jdbcCompliant() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,746 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
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.Connection;
|
||||||
|
import java.sql.Date;
|
||||||
|
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.SQLWarning;
|
||||||
|
import java.sql.SQLXML;
|
||||||
|
import java.sql.Time;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows")
|
||||||
|
public class StubPreparedStatement extends StubStatement implements PreparedStatement {
|
||||||
|
StubPreparedStatement(Connection connection) {
|
||||||
|
super(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery(String sql) throws SQLException {
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMaxFieldSize() throws SQLException {
|
||||||
|
throw new SQLException("Simulated disconnection error", "08999");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setMaxFieldSize(int max) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMaxRows() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setMaxRows(int max) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setEscapeProcessing(boolean enable) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getQueryTimeout() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setQueryTimeout(int seconds) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void cancel() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCursorName(String name) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getResultSet() throws SQLException {
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getUpdateCount() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults() throws SQLException {
|
||||||
|
if (isClosed()) {
|
||||||
|
throw new SQLException("Connection is closed");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFetchDirection(int direction) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getFetchDirection() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFetchSize(int rows) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getFetchSize() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetConcurrency() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetType() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addBatch(String sql) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearBatch() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int[] executeBatch() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults(int current) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getGeneratedKeys() throws SQLException {
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, String[] columnNames) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetHoldability() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setPoolable(boolean poolable) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isPoolable() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void closeOnCompletion() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isCloseOnCompletion() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
if (iface.isInstance(this)) {
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SQLException("Wrapped connection is not an instance of " + iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery() throws SQLException {
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate() throws SQLException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBoolean(int parameterIndex, boolean x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setByte(int parameterIndex, byte x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setShort(int parameterIndex, short x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setInt(int parameterIndex, int x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLong(int parameterIndex, long x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFloat(int parameterIndex, float x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDouble(int parameterIndex, double x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setString(int parameterIndex, String x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBytes(int parameterIndex, byte[] x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearParameters() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute() throws SQLException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addBatch() throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setRef(int parameterIndex, Ref x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, Blob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Clob x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setArray(int parameterIndex, Array x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSetMetaData getMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setURL(int parameterIndex, URL x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ParameterMetaData getParameterMetaData() throws SQLException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setRowId(int parameterIndex, RowId x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNString(int parameterIndex, String value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, NClob value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setNClob(int parameterIndex, Reader reader) throws SQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,418 @@
|
||||||
|
package org.xbib.io.pool.jdbc.mock;
|
||||||
|
|
||||||
|
import org.xbib.io.pool.jdbc.PoolTestExtension;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
public class StubStatement implements Statement {
|
||||||
|
public static volatile boolean oldDriver;
|
||||||
|
|
||||||
|
private static volatile long simulatedQueryTime;
|
||||||
|
private boolean closed;
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
public StubStatement(Connection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSimulatedQueryTime(long time) {
|
||||||
|
simulatedQueryTime = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet executeQuery(String sql) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws SQLException {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMaxFieldSize() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setMaxFieldSize(int max) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMaxRows() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setMaxRows(int max) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setEscapeProcessing(boolean enable) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getQueryTimeout() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setQueryTimeout(int seconds) throws SQLException {
|
||||||
|
if (oldDriver) {
|
||||||
|
throw new SQLFeatureNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void cancel() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearWarnings() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setCursorName(String name) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
if (simulatedQueryTime > 0) {
|
||||||
|
PoolTestExtension.quietlySleep(simulatedQueryTime);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getResultSet() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getUpdateCount() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFetchDirection(int direction) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getFetchDirection() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFetchSize(int rows) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getFetchSize() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetConcurrency() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetType() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addBatch(String sql) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clearBatch() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int[] executeBatch() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean getMoreResults(int current) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ResultSet getGeneratedKeys() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return new StubResultSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean execute(String sql, String[] columnNames) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getResultSetHoldability() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() throws SQLException {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setPoolable(boolean poolable) throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isPoolable() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public void closeOnCompletion() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public boolean isCloseOnCompletion() throws SQLException {
|
||||||
|
checkClosed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClosed() throws SQLException {
|
||||||
|
if (closed) {
|
||||||
|
throw new SQLException("Statement is closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
jdbc-query/build.gradle
Normal file
7
jdbc-query/build.gradle
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
dependencies {
|
||||||
|
api project(":jdbc-connection-pool")
|
||||||
|
testImplementation "org.apache.derby:derby:${project.property('derby.version')}"
|
||||||
|
testImplementation "org.testcontainers:testcontainers:${project.property('testcontainers.version')}"
|
||||||
|
testImplementation "org.testcontainers:junit-jupiter:${project.property('testcontainers.version')}"
|
||||||
|
testImplementation "org.testcontainers:oracle-xe:${project.property('testcontainers.version')}"
|
||||||
|
}
|
4
jdbc-query/src/main/java/module-info.java
Normal file
4
jdbc-query/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module org.xbib.jdbc.query {
|
||||||
|
requires transitive org.xbib.jdbc.connection.pool;
|
||||||
|
exports org.xbib.jdbc.query;
|
||||||
|
}
|
127
jdbc-query/src/main/java/org/xbib/jdbc/query/Config.java
Normal file
127
jdbc-query/src/main/java/org/xbib/jdbc/query/Config.java
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for getting configuration parameters. This isn't intended as
|
||||||
|
* a be-all, end-all configuration solution. Just a way of easily specifying
|
||||||
|
* multiple read-only sources for configuration with a nice fluent syntax.
|
||||||
|
*/
|
||||||
|
public interface Config extends Function<String, String>, Supplier<Config> {
|
||||||
|
/**
|
||||||
|
* Convenience method for fluent syntax.
|
||||||
|
*
|
||||||
|
* @return a builder for specifying from where configuration should be loaded
|
||||||
|
*/
|
||||||
|
static ConfigFrom from() {
|
||||||
|
return new ConfigFromImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add: String originalKey(String key) to find out the key before prefixing or other manipulation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a trimmed, non-empty string, or null
|
||||||
|
*/
|
||||||
|
String getString(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a trimmed, non-empty string
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
String getStringOrThrow(String key);
|
||||||
|
|
||||||
|
String getString(String key, String defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #getString(String)}. Useful for passing configs around
|
||||||
|
* without static dependencies.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default String apply(String key) {
|
||||||
|
return getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Config get() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Integer getInteger(String key);
|
||||||
|
|
||||||
|
int getInteger(String key, int defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
int getIntegerOrThrow(String key);
|
||||||
|
|
||||||
|
|
||||||
|
Long getLong(String key);
|
||||||
|
|
||||||
|
long getLong(String key, long defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
long getLongOrThrow(String key);
|
||||||
|
|
||||||
|
|
||||||
|
Float getFloat(String key);
|
||||||
|
|
||||||
|
float getFloat(String key, float defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
float getFloatOrThrow(String key);
|
||||||
|
|
||||||
|
|
||||||
|
Double getDouble(String key);
|
||||||
|
|
||||||
|
double getDouble(String key, double defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
double getDoubleOrThrow(String key);
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimal(String key);
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimal(String key, BigDecimal defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrThrow(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a boolean value from the configuration. The value is not case-sensitivie,
|
||||||
|
* and may be either true/false or yes/no. If no value was provided or an invalid
|
||||||
|
* value is provided, false will be returned.
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrFalse(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a boolean value from the configuration. The value is not case-sensitivie,
|
||||||
|
* and may be either true/false or yes/no. If no value was provided or an invalid
|
||||||
|
* value is provided, true will be returned.
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrTrue(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ConfigMissingException if no value could be read for the specified key
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrThrow(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show where configuration is coming from. This is useful to drop in your logs
|
||||||
|
* for troubleshooting.
|
||||||
|
*/
|
||||||
|
String sources();
|
||||||
|
}
|
102
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigFrom.java
Normal file
102
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigFrom.java
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.charset.CharsetDecoder;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull configuration properties from various sources and filter/manipulate them.
|
||||||
|
*/
|
||||||
|
public interface ConfigFrom extends Supplier<Config> {
|
||||||
|
/**
|
||||||
|
* Convenience method for fluent syntax.
|
||||||
|
*
|
||||||
|
* @return a builder for specifying from where configuration should be loaded
|
||||||
|
*/
|
||||||
|
static ConfigFrom firstOf() {
|
||||||
|
return new ConfigFromImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config other(Function<String, String> other) {
|
||||||
|
if (other instanceof Config) {
|
||||||
|
return (Config) other;
|
||||||
|
}
|
||||||
|
return new ConfigFromImpl().custom(other::apply).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigFrom custom(Function<String, String> keyValueLookup);
|
||||||
|
|
||||||
|
ConfigFrom value(String key, String value);
|
||||||
|
|
||||||
|
ConfigFrom systemProperties();
|
||||||
|
|
||||||
|
ConfigFrom env();
|
||||||
|
|
||||||
|
ConfigFrom properties(Properties properties);
|
||||||
|
|
||||||
|
ConfigFrom config(Config config);
|
||||||
|
|
||||||
|
ConfigFrom config(Supplier<Config> config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a set of properties files to read from, which can be overridden by a system property "properties".
|
||||||
|
* Equivalent to:
|
||||||
|
* <pre>
|
||||||
|
* defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties")
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
ConfigFrom defaultPropertyFiles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a set of properties files to read from, which can be overridden by a specified system property.
|
||||||
|
* Equivalent to:
|
||||||
|
* <pre>
|
||||||
|
* defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames)
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a set of properties files to read from, which can be overridden by a specified system property.
|
||||||
|
* Equivalent to:
|
||||||
|
* <pre>
|
||||||
|
* propertyFile(Charset.defaultCharset().newDecoder(),
|
||||||
|
* System.getProperty(systemPropertyKey, String.join(File.pathSeparator, filenames))
|
||||||
|
* .split(File.pathSeparator));
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames);
|
||||||
|
|
||||||
|
ConfigFrom propertyFile(String... filenames);
|
||||||
|
|
||||||
|
ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames);
|
||||||
|
|
||||||
|
ConfigFrom propertyFile(File... files);
|
||||||
|
|
||||||
|
ConfigFrom propertyFile(CharsetDecoder decoder, File... files);
|
||||||
|
|
||||||
|
ConfigFrom rename(String key, String newKey);
|
||||||
|
|
||||||
|
ConfigFrom includeKeys(String... keys);
|
||||||
|
|
||||||
|
ConfigFrom includePrefix(String... prefixes);
|
||||||
|
|
||||||
|
ConfigFrom includeRegex(String regex);
|
||||||
|
|
||||||
|
ConfigFrom excludeKeys(String... keys);
|
||||||
|
|
||||||
|
ConfigFrom excludePrefix(String... prefixes);
|
||||||
|
|
||||||
|
ConfigFrom excludeRegex(String regex);
|
||||||
|
|
||||||
|
ConfigFrom removePrefix(String... prefixes);
|
||||||
|
|
||||||
|
ConfigFrom addPrefix(String prefix);
|
||||||
|
|
||||||
|
ConfigFrom substitutions(Config config);
|
||||||
|
|
||||||
|
Config get();
|
||||||
|
|
||||||
|
}
|
295
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigFromImpl.java
Normal file
295
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigFromImpl.java
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.CharsetDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access configuration properties from a variety of standard sources,
|
||||||
|
* and provide some basic filtering and mapping of property keys.
|
||||||
|
*/
|
||||||
|
public class ConfigFromImpl implements ConfigFrom {
|
||||||
|
|
||||||
|
private final List<Config> searchPath = new ArrayList<>();
|
||||||
|
|
||||||
|
public ConfigFromImpl() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigFromImpl(Config first) {
|
||||||
|
searchPath.add(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom custom(Function<String, String> keyValueLookup) {
|
||||||
|
return custom(keyValueLookup, "custom()");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConfigFrom custom(Function<String, String> keyValueLookup, String source) {
|
||||||
|
searchPath.add(new ConfigImpl(keyValueLookup, source));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom value(String key, String value) {
|
||||||
|
return custom(k -> k.equals(key) ? value : null, "value(" + key + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom config(Config config) {
|
||||||
|
searchPath.add(config);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom config(Supplier<Config> config) {
|
||||||
|
return config(config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom systemProperties() {
|
||||||
|
return custom(System::getProperty, "systemProperties()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom env() {
|
||||||
|
return custom(System::getenv, "env()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom properties(Properties properties) {
|
||||||
|
return custom(properties::getProperty, "properties()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom defaultPropertyFiles() {
|
||||||
|
return defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames) {
|
||||||
|
return defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames) {
|
||||||
|
String properties = System.getProperty(systemPropertyKey, String.join(File.pathSeparator, filenames));
|
||||||
|
return propertyFile(Charset.defaultCharset().newDecoder(), properties.split(File.pathSeparator));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom propertyFile(String... filenames) {
|
||||||
|
return propertyFile(Charset.defaultCharset().newDecoder(), filenames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames) {
|
||||||
|
for (String filename : filenames) {
|
||||||
|
if (filename != null) {
|
||||||
|
propertyFile(decoder, new File(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom propertyFile(File... files) {
|
||||||
|
return propertyFile(Charset.defaultCharset().newDecoder(), files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom propertyFile(CharsetDecoder decoder, File... files) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
try (
|
||||||
|
FileInputStream fis = new FileInputStream(file);
|
||||||
|
InputStreamReader reader = new InputStreamReader(fis, decoder)
|
||||||
|
) {
|
||||||
|
properties.load(reader);
|
||||||
|
}
|
||||||
|
searchPath.add(new ConfigImpl(properties::getProperty, "propertyFile(" + file.getAbsolutePath() + ")"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Put a "fake" provider in so we can see it failed
|
||||||
|
String fileName = file.getName();
|
||||||
|
try {
|
||||||
|
fileName = file.getAbsolutePath();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// Fall back to relative name
|
||||||
|
}
|
||||||
|
custom(k -> null, "Ignored: propertyFile(" + fileName + ") " + e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom rename(String configKey, String newKey) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
if (key.equals(configKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (key.equals(newKey)) {
|
||||||
|
return lookup(configKey);
|
||||||
|
}
|
||||||
|
return lookup(key);
|
||||||
|
}, indentedSources("rename(" + configKey + " -> " + newKey + ")")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom includeKeys(String... keys) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
for (String k : keys) {
|
||||||
|
if (key.equals(k)) {
|
||||||
|
return lookup(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, indentedSources("includeKeys" + Arrays.asList(keys))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom includePrefix(String... prefixes) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
return lookup(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, indentedSources("includePrefix" + Arrays.asList(prefixes))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom includeRegex(String regex) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
if (key.matches(regex)) {
|
||||||
|
return lookup(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, indentedSources("includeRegex(" + regex + ")")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom excludeKeys(String... keys) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
for (String k : keys) {
|
||||||
|
if (key.equals(k)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lookup(key);
|
||||||
|
}, indentedSources("excludeKeys" + Arrays.asList(keys))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom excludePrefix(String... prefixes) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lookup(key);
|
||||||
|
}, indentedSources("excludePrefix" + Arrays.asList(prefixes))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom excludeRegex(String regex) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
if (key.matches(regex)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return lookup(key);
|
||||||
|
}, indentedSources("excludeRegex(" + regex + ")")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom removePrefix(String... prefixes) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
// Give precedence to ones that already lacked the prefix,
|
||||||
|
// do an include*() first if you don't want that
|
||||||
|
String value = lookup(key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String prefix : prefixes) {
|
||||||
|
value = lookup(prefix + key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, indentedSources("removePrefix" + Arrays.asList(prefixes))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom addPrefix(String prefix) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
if (key.startsWith(prefix)) {
|
||||||
|
return lookup(key.substring(prefix.length()));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, indentedSources("addPrefix(" + prefix + ")")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigFrom substitutions(Config config) {
|
||||||
|
return new ConfigFromImpl(new ConfigImpl(key -> {
|
||||||
|
String value = lookup(key);
|
||||||
|
if (value != null) {
|
||||||
|
// matches ${ENV_VAR_NAME} or $ENV_VAR_NAME
|
||||||
|
Pattern p = Pattern.compile("(?<!\\$)\\$(?!\\$)\\{(\\w+)}|(?<!\\$)\\$(?!\\$)(\\w+)");
|
||||||
|
Matcher m = p.matcher(value);
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while (m.find()) {
|
||||||
|
String envVarName = null == m.group(1) ? m.group(2) : m.group(1);
|
||||||
|
String envVarValue = config.getString(envVarName);
|
||||||
|
m.appendReplacement(sb, null == envVarValue ? "" : envVarValue);
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
// Allow escaping literal $ with $$
|
||||||
|
return sb.toString().replaceAll("(\\${2})", "\\$");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, indentedSources("substitutions(" + config.sources() + ")")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Config get() {
|
||||||
|
return new ConfigImpl(this::lookup, indentedSources("Config"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String indentedSources(String label) {
|
||||||
|
StringBuilder buf = new StringBuilder(label);
|
||||||
|
for (Config config : searchPath) {
|
||||||
|
buf.append(config.sources().replaceAll("(?s)^|\\n", "\n "));
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String lookup(String key) {
|
||||||
|
for (Config config : searchPath) {
|
||||||
|
String value = config.getString(key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
252
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigImpl.java
Normal file
252
jdbc-query/src/main/java/org/xbib/jdbc/query/ConfigImpl.java
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles all of the type conversions, default values, etc.
|
||||||
|
* to get from simple string key/value pairs to richer configuration.
|
||||||
|
*/
|
||||||
|
public class ConfigImpl implements Config {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(ConfigFromImpl.class.getName());
|
||||||
|
|
||||||
|
private final Function<String, String> provider;
|
||||||
|
|
||||||
|
private final String sources;
|
||||||
|
|
||||||
|
private final Set<String> failedKeys = new HashSet<>();
|
||||||
|
|
||||||
|
public ConfigImpl(Function<String, String> provider, String sources) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.sources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(String key) {
|
||||||
|
return cleanString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrThrow(String key) {
|
||||||
|
return nonnull(key, getString(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getString(String key, String defaultValue) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
if (stringValue != null) {
|
||||||
|
return stringValue;
|
||||||
|
}
|
||||||
|
// Make sure the default value is tidied the same way a value would be
|
||||||
|
defaultValue = defaultValue.trim();
|
||||||
|
if (defaultValue.length() == 0) {
|
||||||
|
throw new IllegalArgumentException("Your default value is empty or just whitespace");
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getInteger(String key) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
try {
|
||||||
|
return stringValue == null ? null : Integer.parseInt(stringValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInteger(String key, int defaultValue) {
|
||||||
|
Integer value = getInteger(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntegerOrThrow(String key) {
|
||||||
|
return nonnull(key, getInteger(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLong(String key) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
try {
|
||||||
|
return stringValue == null ? null : Long.parseLong(stringValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLong(String key, long defaultValue) {
|
||||||
|
Long value = getLong(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLongOrThrow(String key) {
|
||||||
|
return nonnull(key, getLong(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getFloat(String key) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
try {
|
||||||
|
return stringValue == null ? null : Float.parseFloat(stringValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloat(String key, float defaultValue) {
|
||||||
|
Float value = getFloat(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloatOrThrow(String key) {
|
||||||
|
return nonnull(key, getFloat(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getDouble(String key) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
try {
|
||||||
|
return stringValue == null ? null : Double.parseDouble(stringValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDouble(String key, double defaultValue) {
|
||||||
|
Double value = getDouble(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDoubleOrThrow(String key) {
|
||||||
|
return nonnull(key, getDouble(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimal(String key) {
|
||||||
|
String stringValue = cleanString(key);
|
||||||
|
try {
|
||||||
|
return stringValue == null ? null : new BigDecimal(stringValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
|
||||||
|
BigDecimal value = getBigDecimal(key);
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrThrow(String key) {
|
||||||
|
return nonnull(key, getBigDecimal(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrFalse(String key) {
|
||||||
|
return parseBoolean(cleanString(key), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrTrue(String key) {
|
||||||
|
return parseBoolean(cleanString(key), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrThrow(String key) {
|
||||||
|
String value = nonnull(key, cleanString(key));
|
||||||
|
value = value.toLowerCase();
|
||||||
|
if (value.equals("yes") || value.equals("true")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value.equals("no") || value.equals("false")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new ConfigMissingException("Unrecognized boolean value for config key: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T nonnull(String key, T value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new ConfigMissingException("No value for config key: " + key);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseBoolean(String value, boolean defaultValue) {
|
||||||
|
if (value != null) {
|
||||||
|
value = value.toLowerCase();
|
||||||
|
if (value.equals("yes") || value.equals("true")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value.equals("no") || value.equals("false")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanString(String key) {
|
||||||
|
String value = null;
|
||||||
|
try {
|
||||||
|
value = provider.apply(key);
|
||||||
|
if (value != null) {
|
||||||
|
value = value.trim();
|
||||||
|
if (value.length() == 0) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!failedKeys.contains(key)) {
|
||||||
|
log.log(Level.SEVERE, "Could not load config value for key (this message will only be logged once): " + key, e);
|
||||||
|
failedKeys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a configuration value is present but not in a usable format.
|
||||||
|
* For example, if the value must be an integer and the configuration value
|
||||||
|
* is a non-numeric string.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class ConfigInvalidException extends DatabaseException {
|
||||||
|
|
||||||
|
public ConfigInvalidException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigInvalidException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigInvalidException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a configuration value is required but was not present.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class ConfigMissingException extends DatabaseException {
|
||||||
|
|
||||||
|
public ConfigMissingException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigMissingException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigMissingException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception will be thrown when a condition arises that violates
|
||||||
|
* a stated invariant regarding the database. This might be a database
|
||||||
|
* schema "constraint violated" as thrown by the database, or could be
|
||||||
|
* caused by a violation of constraints enforced only within the code.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class ConstraintViolationException extends DatabaseException {
|
||||||
|
|
||||||
|
public ConstraintViolationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConstraintViolationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConstraintViolationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
211
jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java
Normal file
211
jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary class for accessing a relational (SQL) database.
|
||||||
|
*/
|
||||||
|
public interface Database extends Supplier<Database> {
|
||||||
|
/**
|
||||||
|
* Create a SQL "insert" statement for further manipulation and execution.
|
||||||
|
* Note this call does not actually execute the SQL.
|
||||||
|
*
|
||||||
|
* @param sql the SQL to execute, optionally containing indexed ("?") or
|
||||||
|
* named (":foo") parameters. To include the characters '?' or ':'
|
||||||
|
* in the SQL you must escape them with two ("??" or "::"). You
|
||||||
|
* MUST be careful not to pass untrusted strings in as SQL, since
|
||||||
|
* this will be executed in the database.
|
||||||
|
* @return an interface for further manipulating the statement; never null
|
||||||
|
*/
|
||||||
|
SqlInsert toInsert(String sql);
|
||||||
|
|
||||||
|
SqlInsert toInsert( Sql sql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL "select" statement for further manipulation and execution.
|
||||||
|
* Note this call does not actually execute the SQL.
|
||||||
|
*
|
||||||
|
* @param sql the SQL to execute, optionally containing indexed ("?") or
|
||||||
|
* named (":foo") parameters. To include the characters '?' or ':'
|
||||||
|
* in the SQL you must escape them with two ("??" or "::"). You
|
||||||
|
* MUST be careful not to pass untrusted strings in as SQL, since
|
||||||
|
* this will be executed in the database.
|
||||||
|
* @return an interface for further manipulating the statement; never null
|
||||||
|
*/
|
||||||
|
SqlSelect toSelect(String sql);
|
||||||
|
|
||||||
|
SqlSelect toSelect( Sql sql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL "update" statement for further manipulation and execution.
|
||||||
|
* Note this call does not actually execute the SQL.
|
||||||
|
*
|
||||||
|
* @param sql the SQL to execute, optionally containing indexed ("?") or
|
||||||
|
* named (":foo") parameters. To include the characters '?' or ':'
|
||||||
|
* in the SQL you must escape them with two ("??" or "::"). You
|
||||||
|
* MUST be careful not to pass untrusted strings in as SQL, since
|
||||||
|
* this will be executed in the database.
|
||||||
|
* @return an interface for further manipulating the statement; never null
|
||||||
|
*/
|
||||||
|
SqlUpdate toUpdate(String sql);
|
||||||
|
|
||||||
|
SqlUpdate toUpdate( Sql sql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SQL "delete" statement for further manipulation and execution.
|
||||||
|
* Note this call does not actually execute the SQL.
|
||||||
|
*
|
||||||
|
* @param sql the SQL to execute, optionally containing indexed ("?") or
|
||||||
|
* named (":foo") parameters. To include the characters '?' or ':'
|
||||||
|
* in the SQL you must escape them with two ("??" or "::"). You
|
||||||
|
* MUST be careful not to pass untrusted strings in as SQL, since
|
||||||
|
* this will be executed in the database.
|
||||||
|
* @return an interface for further manipulating the statement; never null
|
||||||
|
*/
|
||||||
|
SqlUpdate toDelete(String sql);
|
||||||
|
|
||||||
|
SqlUpdate toDelete( Sql sql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a DDL (schema modifying) statement for further manipulation and execution.
|
||||||
|
* Note this call does not actually execute the SQL.
|
||||||
|
*
|
||||||
|
* @param sql the SQL to execute, optionally containing indexed ("?") or
|
||||||
|
* named (":foo") parameters. To include the characters '?' or ':'
|
||||||
|
* in the SQL you must escape them with two ("??" or "::"). You
|
||||||
|
* MUST be careful not to pass untrusted strings in as SQL, since
|
||||||
|
* this will be executed in the database.
|
||||||
|
* @return an interface for further manipulating the statement; never null
|
||||||
|
*/
|
||||||
|
Ddl ddl(String sql);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next value from a sequence. This method helps smooth over the
|
||||||
|
* syntax differences across databases.
|
||||||
|
*/
|
||||||
|
Long nextSequenceValue( String sequenceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value that would be used if you specify an argNowPerApp() parameter.
|
||||||
|
*/
|
||||||
|
Date nowPerApp();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause the underlying connection to commit its transaction immediately. This
|
||||||
|
* must be explicitly enabled (see {@link Options},
|
||||||
|
* or it will throw a {@link DatabaseException}.
|
||||||
|
*/
|
||||||
|
void commitNow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cause the underlying connection to roll back its transaction immediately. This
|
||||||
|
* must be explicitly enabled (see {@link Options},
|
||||||
|
* or it will throw a {@link DatabaseException}.
|
||||||
|
*/
|
||||||
|
void rollbackNow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Obtain direct access to the connection being used by this instance. Be very
|
||||||
|
* careful as this is highly likely to be unsafe and cause you great pain and
|
||||||
|
* suffering. This method is included to help ease into the library in large
|
||||||
|
* codebases where some parts still rely on direct JDBC access.</p>
|
||||||
|
*
|
||||||
|
* <p>By default this method will throw a {@link DatabaseException}. If you want
|
||||||
|
* to use this method you must explicitly enable it via
|
||||||
|
* {@link Options#allowConnectionAccess()}</p>
|
||||||
|
*/
|
||||||
|
Connection underlyingConnection();
|
||||||
|
|
||||||
|
Options options();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access information about what kind of database we are dealing with.
|
||||||
|
*/
|
||||||
|
Flavor flavor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A little syntax sugar to make it easier to customize your SQL based on the
|
||||||
|
* specific database. For example:</p>
|
||||||
|
*
|
||||||
|
* <pre>"select 1" + db.when().oracle(" from dual")</pre>
|
||||||
|
* <pre>"select " + db.when().postgres("date_trunc('day',").other("trunc(") + ") ..."</pre>
|
||||||
|
*
|
||||||
|
* @return an interface for chaining or terminating the conditionals
|
||||||
|
*/
|
||||||
|
When when();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to deal with mutually incompatible syntax for this. For example:
|
||||||
|
*
|
||||||
|
* <p>Oracle: 'drop sequence x'</p>
|
||||||
|
* <p>Derby: 'drop sequence x restrict'</p>"
|
||||||
|
*/
|
||||||
|
void dropSequenceQuietly(String sequenceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to deal with dropping tables that may or may not exist. Some
|
||||||
|
* databases make it hard to check and conditionally drop things, so we will just
|
||||||
|
* try to drop it and ignore the errors.
|
||||||
|
*
|
||||||
|
* @param tableName the table to be dropped
|
||||||
|
*/
|
||||||
|
void dropTableQuietly(String tableName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to check if a table or view exists so that caller can decide
|
||||||
|
* whether to create or update a table. The table name's case is normalized using
|
||||||
|
* the database's convention unless tableName is enclosed in double quotes.
|
||||||
|
* The default catalog and schema from the DB connection will be used.
|
||||||
|
*
|
||||||
|
* @param tableName the table to be checked
|
||||||
|
* @return true if the table or view exists
|
||||||
|
*/
|
||||||
|
boolean tableExists( String tableName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to check whether a table or view exists or not.
|
||||||
|
* The table name's case is normalized using the database's convention
|
||||||
|
* unless tableName is enclosed in double quotes. The default catalog
|
||||||
|
* from the DB connection will be used.
|
||||||
|
*
|
||||||
|
* @param tableName the table to be checked
|
||||||
|
* @param schemaName the schema expected to contain the table
|
||||||
|
* @return true if the table or view exists
|
||||||
|
*/
|
||||||
|
boolean tableExists( String tableName, String schemaName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the DB table name in the normalized form in which it is stored.
|
||||||
|
* Databases like Oracle, Derby, HSQL store their tables in upper case.
|
||||||
|
* Databases like postgres and sqlserver use lower case unless configured otherwise.
|
||||||
|
* If the caller passes in a quoted string, we will leave the name as is, removing
|
||||||
|
* the quotes.
|
||||||
|
*
|
||||||
|
* @param tableName this should be a name, not a pattern
|
||||||
|
* @return table name in appropriate format for DB lookup - original case, uppercase, or lowercase
|
||||||
|
*/
|
||||||
|
String normalizeTableName(String tableName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the JVM time (and timezone) against the database and log a warning
|
||||||
|
* or throw an error if they are too far apart. It is a good idea to do this
|
||||||
|
* before you store and dates, and maybe make it part of your health checks.
|
||||||
|
* If the clocks differ by more than an hour, a DatabaseException is thrown
|
||||||
|
* suggesting you check the timezones (under the assumptions the JVM and
|
||||||
|
* database are running in different timezones).
|
||||||
|
*
|
||||||
|
* @param millisToWarn if the clocks disagree by more than this and less than
|
||||||
|
* millisToError, a warning will be dropped in the log
|
||||||
|
* @param millisToError if the clocks disagree by more than this a
|
||||||
|
* DatabaseEception will be thrown
|
||||||
|
*/
|
||||||
|
void assertTimeSynchronized(long millisToWarn, long millisToError);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method, same as {@link #assertTimeSynchronized(long, long)}
|
||||||
|
* with millisToWarn=10000 and millisToError=30000.
|
||||||
|
*/
|
||||||
|
void assertTimeSynchronized();
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates something went wrong accessing the database. Most often this is
|
||||||
|
* used to wrap SQLException to avoid declaring checked exceptions.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class DatabaseException extends RuntimeException {
|
||||||
|
|
||||||
|
public DatabaseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an exception with a DatabaseException, taking into account all known
|
||||||
|
* subtypes such that we wrap subtypes in a matching type (so we don't obscure
|
||||||
|
* the type available to catch clauses).
|
||||||
|
*
|
||||||
|
* @param message the new wrapping exception will have this message
|
||||||
|
* @param cause the exception to be wrapped
|
||||||
|
* @return the exception you should throw
|
||||||
|
*/
|
||||||
|
public static DatabaseException wrap(String message, Throwable cause) {
|
||||||
|
if (cause instanceof ConstraintViolationException) {
|
||||||
|
return new ConstraintViolationException(message, cause);
|
||||||
|
}
|
||||||
|
return new DatabaseException(message, cause);
|
||||||
|
}
|
||||||
|
}
|
280
jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java
Normal file
280
jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary class for accessing a relational (SQL) database.
|
||||||
|
*/
|
||||||
|
public class DatabaseImpl implements Database {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(Database.class.getName());
|
||||||
|
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
private final Options options;
|
||||||
|
|
||||||
|
public DatabaseImpl(Connection connection, Options options) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseImpl(Flavor flavor) {
|
||||||
|
this(new OptionsDefault(flavor));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseImpl(Options options) {
|
||||||
|
this.connection = null;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DatabaseImpl get() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlInsert toInsert(String sql) {
|
||||||
|
return new SqlInsertImpl(connection, sql, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlInsert toInsert(Sql sql) {
|
||||||
|
return new SqlInsertImpl(connection, sql.sql(), options).apply(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlSelect toSelect(String sql) {
|
||||||
|
return new SqlSelectImpl(connection, sql, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlSelect toSelect(Sql sql) {
|
||||||
|
return new SqlSelectImpl(connection, sql.sql(), options).apply(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlUpdate toUpdate(String sql) {
|
||||||
|
return new SqlUpdateImpl(connection, sql, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlUpdate toUpdate(Sql sql) {
|
||||||
|
return new SqlUpdateImpl(connection, sql.sql(), options).apply(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlUpdate toDelete(String sql) {
|
||||||
|
return new SqlUpdateImpl(connection, sql, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlUpdate toDelete(Sql sql) {
|
||||||
|
return new SqlUpdateImpl(connection, sql.sql(), options).apply(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Ddl ddl(String sql) {
|
||||||
|
return new DdlImpl(connection, sql, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long nextSequenceValue(/*@Untainted*/ String sequenceName) {
|
||||||
|
return toSelect(flavor().sequenceSelectNextVal(sequenceName)).queryLongOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date nowPerApp() {
|
||||||
|
return options.currentDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commitNow() {
|
||||||
|
if (options.ignoreTransactionControl()) {
|
||||||
|
log.fine("Ignoring call to commitNow()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!options.allowTransactionControl()) {
|
||||||
|
throw new DatabaseException("Calls to commitNow() are not allowed");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
connection.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DatabaseException("Unable to commit transaction", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rollbackNow() {
|
||||||
|
if (options.ignoreTransactionControl()) {
|
||||||
|
log.fine("Ignoring call to rollbackNow()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!options.allowTransactionControl()) {
|
||||||
|
throw new DatabaseException("Calls to rollbackNow() are not allowed");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
connection.rollback();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new DatabaseException("Unable to rollback transaction", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection underlyingConnection() {
|
||||||
|
if (!options.allowConnectionAccess()) {
|
||||||
|
throw new DatabaseException("Calls to underlyingConnection() are not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Options options() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flavor flavor() {
|
||||||
|
return options.flavor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public When when() {
|
||||||
|
return new When(options.flavor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dropSequenceQuietly(/*@Untainted*/ String sequenceName) {
|
||||||
|
ddl(flavor().sequenceDrop(sequenceName)).executeQuietly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dropTableQuietly(/*@Untainted*/ String tableName) {
|
||||||
|
if (flavor() == Flavor.postgresql || flavor() == Flavor.hsqldb) {
|
||||||
|
ddl("drop table if exists " + tableName).executeQuietly();
|
||||||
|
} else {
|
||||||
|
ddl("drop table " + tableName).executeQuietly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tableExists(String tableName) throws DatabaseException {
|
||||||
|
|
||||||
|
String schemaName = null;
|
||||||
|
Method getSchema = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use reflections to see if connection.getSchema API exists. It should exist for any JDBC7 or later implementation
|
||||||
|
// We still support Oracle 11 with odbc6, however, so we can't assume it's there.
|
||||||
|
getSchema = connection.getClass().getDeclaredMethod("getSchema");
|
||||||
|
} catch (NoSuchMethodException noMethodExc) {
|
||||||
|
// Expected if method does not exist - just let it go
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (getSchema != null) {
|
||||||
|
schemaName = ((String) getSchema.invoke(connection, new Object[0]));
|
||||||
|
} else if (flavor() == Flavor.oracle) {
|
||||||
|
// Oracle defaults to user name schema - use that.
|
||||||
|
log.warning("Connection getSchema API was not found. Defaulting to Oracle user name schema." +
|
||||||
|
"If this is not appropriate, please use tableExists(tableName, schemaName) API or upgrade to ojdbc7 or later");
|
||||||
|
schemaName = connection.getMetaData().getUserName();
|
||||||
|
}
|
||||||
|
if (schemaName == null) {
|
||||||
|
// connection.getSchema API was supported starting at JDK1.7. Method should not be null.
|
||||||
|
throw new NullPointerException("Unable to retrieve schema name.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception exc) {
|
||||||
|
throw new DatabaseException("Unable to determine the schema. " +
|
||||||
|
"Please use tableExists(tableName, schemaName API) or upgrade to a JDBC7 driver or later.", exc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableExists(tableName, schemaName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tableExists(String tableName, String schemaName) throws DatabaseException {
|
||||||
|
if (tableName != null) {
|
||||||
|
try {
|
||||||
|
DatabaseMetaData metadata = connection.getMetaData();
|
||||||
|
String normalizedTable = normalizeTableName(tableName);
|
||||||
|
ResultSet resultSet =
|
||||||
|
metadata.getTables(connection.getCatalog(), schemaName, normalizedTable, new String[]{"TABLE", "VIEW"});
|
||||||
|
|
||||||
|
while (resultSet.next()) {
|
||||||
|
if (normalizedTable.equals(resultSet.getString("TABLE_NAME"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException exc) {
|
||||||
|
throw new DatabaseException("Unable to look up table " + tableName
|
||||||
|
+ " in schema " + schemaName + " : " + exc.getMessage(),
|
||||||
|
exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String normalizeTableName(String tableName) {
|
||||||
|
if (tableName == null) {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user gave us a quoted string, leave it alone for look up
|
||||||
|
if (tableName.length() > 2) {
|
||||||
|
if (tableName.startsWith("\"") && tableName.endsWith("\"")) {
|
||||||
|
// Remove quotes and return as is.
|
||||||
|
return tableName.substring(1, tableName.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flavor().isNormalizedUpperCase()) {
|
||||||
|
return tableName.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertTimeSynchronized(long millisToWarn, long millisToError) {
|
||||||
|
toSelect("select ?" + flavor().fromAny())
|
||||||
|
.argDateNowPerDb().queryFirstOrNull(r -> {
|
||||||
|
Date appDate = nowPerApp();
|
||||||
|
Date dbDate = r.getDateOrNull();
|
||||||
|
if (dbDate == null) {
|
||||||
|
throw new DatabaseException("Expecting a date in the result");
|
||||||
|
}
|
||||||
|
if (Math.abs(appDate.getTime() - dbDate.getTime()) > 3600000) {
|
||||||
|
throw new DatabaseException("App and db time are over an hour apart (check your timezones) app: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
|
||||||
|
}
|
||||||
|
if (Math.abs(appDate.getTime() - dbDate.getTime()) > millisToError) {
|
||||||
|
throw new DatabaseException("App and db time over " + millisToError + " millis apart (check your clocks) app: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
|
||||||
|
}
|
||||||
|
if (Math.abs(appDate.getTime() - dbDate.getTime()) > millisToWarn) {
|
||||||
|
log.warning("App and db time are over " + millisToWarn + " millis apart (check your clocks) app: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(appDate.toInstant()) + " db: "
|
||||||
|
+ DateTimeFormatter.ISO_INSTANT.format(dbDate.toInstant()));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertTimeSynchronized() {
|
||||||
|
assertTimeSynchronized(10000, 30000);
|
||||||
|
}
|
||||||
|
}
|
1107
jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java
Normal file
1107
jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java
Normal file
File diff suppressed because it is too large
Load diff
18
jdbc-query/src/main/java/org/xbib/jdbc/query/DbCode.java
Normal file
18
jdbc-query/src/main/java/org/xbib/jdbc/query/DbCode.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of runnable code using a transacted Database.
|
||||||
|
*/
|
||||||
|
public interface DbCode {
|
||||||
|
/**
|
||||||
|
* Implement this method to provide a block of code that uses the provided database
|
||||||
|
* and is transacted. Whether the transaction will commit or rollback is typically
|
||||||
|
* controlled by the code that invokes this method.
|
||||||
|
*
|
||||||
|
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
|
||||||
|
* a DatabaseException (if it is not already one), and then propagated.</p>
|
||||||
|
*/
|
||||||
|
void run(Supplier<Database> dbs) throws Exception;
|
||||||
|
}
|
19
jdbc-query/src/main/java/org/xbib/jdbc/query/DbCodeTx.java
Normal file
19
jdbc-query/src/main/java/org/xbib/jdbc/query/DbCodeTx.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of runnable code using a transacted Database.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DbCodeTx {
|
||||||
|
/**
|
||||||
|
* Implement this method to provide a block of code that uses the provided database
|
||||||
|
* and is transacted. Whether the transaction will commit or rollback is typically
|
||||||
|
* controlled by the code that invokes this method.
|
||||||
|
*
|
||||||
|
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
|
||||||
|
* a DatabaseException (if it is not already one), and then propagated.</p>
|
||||||
|
*/
|
||||||
|
void run(Supplier<Database> db, Transaction tx) throws Exception;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of runnable code using a transacted Database.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DbCodeTyped<T> {
|
||||||
|
/**
|
||||||
|
* Implement this method to provide a block of code that uses the provided database
|
||||||
|
* and is transacted. Whether the transaction will commit or rollback is typically
|
||||||
|
* controlled by the code that invokes this method.
|
||||||
|
*
|
||||||
|
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
|
||||||
|
* a DatabaseException (if it is not already one), and then propagated.</p>
|
||||||
|
*/
|
||||||
|
T run(Supplier<Database> dbs) throws Exception;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of runnable code using a transacted Database.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface DbCodeTypedTx<T> {
|
||||||
|
/**
|
||||||
|
* Implement this method to provide a block of code that uses the provided database
|
||||||
|
* and is transacted. Whether the transaction will commit or rollback is typically
|
||||||
|
* controlled by the code that invokes this method.
|
||||||
|
*
|
||||||
|
* <p>If a {@link Throwable} is thrown from this method, it will be caught, wrapped in
|
||||||
|
* a DatabaseException (if it is not already one), and then propagated.</p>
|
||||||
|
*/
|
||||||
|
T run(Supplier<Database> db, Transaction tx) throws Exception;
|
||||||
|
}
|
19
jdbc-query/src/main/java/org/xbib/jdbc/query/Ddl.java
Normal file
19
jdbc-query/src/main/java/org/xbib/jdbc/query/Ddl.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for executing a chunk of DDL within the database.
|
||||||
|
*/
|
||||||
|
public interface Ddl {
|
||||||
|
/**
|
||||||
|
* Execute the DDL statement. All checked SQLExceptions get wrapped in DatabaseExceptions.
|
||||||
|
*/
|
||||||
|
void execute();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This just does an execute() call and silently discards any DatabaseException
|
||||||
|
* that might occur. This can be useful for things like drop statements, where
|
||||||
|
* some databases don't make it easy to conditionally drop things only if they
|
||||||
|
* exist.
|
||||||
|
*/
|
||||||
|
void executeQuietly();
|
||||||
|
}
|
97
jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java
Normal file
97
jdbc-query/src/main/java/org/xbib/jdbc/query/DdlImpl.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.query.util.DebugSql;
|
||||||
|
import org.xbib.jdbc.query.util.Metric;
|
||||||
|
|
||||||
|
import java.sql.CallableStatement;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class DdlImpl implements Ddl {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(Database.class.getName());
|
||||||
|
|
||||||
|
private static final Logger logQuiet = Logger.getLogger(Database.class.getName() + ".quiet");
|
||||||
|
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
private final String sql;
|
||||||
|
|
||||||
|
private final Options options;
|
||||||
|
|
||||||
|
DdlImpl(Connection connection, String sql, Options options) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.sql = sql;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInternal(boolean quiet) {
|
||||||
|
CallableStatement ps = null;
|
||||||
|
Metric metric = new Metric(log.isLoggable(Level.FINE));
|
||||||
|
|
||||||
|
boolean isSuccess = false;
|
||||||
|
String errorCode = null;
|
||||||
|
Exception logEx = null;
|
||||||
|
try {
|
||||||
|
ps = connection.prepareCall(sql);
|
||||||
|
|
||||||
|
metric.checkpoint("prep");
|
||||||
|
ps.execute();
|
||||||
|
metric.checkpoint("exec");
|
||||||
|
isSuccess = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
errorCode = options.generateErrorCode();
|
||||||
|
logEx = e;
|
||||||
|
throw DatabaseException.wrap(DebugSql.exceptionMessage(sql, null, errorCode, options), e);
|
||||||
|
} finally {
|
||||||
|
close(ps);
|
||||||
|
metric.checkpoint("close");
|
||||||
|
// PostgreSQL requires explicit commit since we are running with setAutoCommit(false)
|
||||||
|
commit(connection);
|
||||||
|
metric.done("commit");
|
||||||
|
if (isSuccess) {
|
||||||
|
DebugSql.logSuccess("DDL", log, metric, sql, null, options);
|
||||||
|
} else if (quiet) {
|
||||||
|
DebugSql.logWarning("DDL", logQuiet, metric, errorCode, sql, null, options, logEx);
|
||||||
|
} else {
|
||||||
|
DebugSql.logError("DDL", log, metric, errorCode, sql, null, options, logEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
updateInternal(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeQuietly() {
|
||||||
|
try {
|
||||||
|
updateInternal(true);
|
||||||
|
} catch (DatabaseException e) {
|
||||||
|
// Ignore, as requested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(Statement s) {
|
||||||
|
if (s != null) {
|
||||||
|
try {
|
||||||
|
s.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.SEVERE, "Caught exception closing the Statement", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commit(Connection c) {
|
||||||
|
if (c != null) {
|
||||||
|
try {
|
||||||
|
c.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.SEVERE, "Caught exception on commit", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
993
jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java
Normal file
993
jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java
Normal file
|
@ -0,0 +1,993 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of supported databases with various compatibility settings.
|
||||||
|
*/
|
||||||
|
public enum Flavor {
|
||||||
|
|
||||||
|
derby {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "integer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
return "char(1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "bigint";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "real";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "double";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric(" + size + "," + precision + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "varchar(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "char(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "clob";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "blob";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
return "next value for " + sequenceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
return "values next value for " + sequenceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
return "drop sequence " + dbtestSeq + " restrict";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
return cycle ? " cycle" : " no cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "current_timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return " from sysibm.sysdummy1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return "timestamp('" + dateFormat.format(date) + "')";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
return "'" + date.toString() + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
return " as bigint";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sqlserver {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "float(24)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "float(53)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric(" + size + "," + precision + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "numeric(10)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
return "char(1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "numeric(19)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "datetime2(3)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
return "next value for " + sequenceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
return "select next value for " + sequenceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
return "drop sequence " + dbtestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "varchar(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "char(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "varchar(max)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "varbinary(max)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
// Not supported
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
return cycle ? " cycle" : " no cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
// TODO it probably does, but I haven't figure it out yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "current_timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
if (nbrValuesToCache < 2) {
|
||||||
|
return " no cache";
|
||||||
|
}
|
||||||
|
return " cache " + nbrValuesToCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return "cast('" + dateFormat.format(date) + "' as datetime2(3))";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
return "'" + date.toString() + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oracle {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "binary_float";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "binary_double";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric(" + size + "," + precision + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "numeric(10)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
return "char(1 char)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "numeric(19)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "timestamp(3)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
return sequenceName + ".nextval";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
return "select " + sequenceName + ".nextval from dual";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
return "drop sequence " + dbtestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "varchar2(" + length + " char)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "char(" + length + " char)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "clob";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "blob";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
return order ? " order" : " noorder";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
return cycle ? " cycle" : " nocycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "systimestamp(3)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
if (nbrValuesToCache < 2) {
|
||||||
|
return " nocache";
|
||||||
|
}
|
||||||
|
return " cache " + nbrValuesToCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return " from dual";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return "timestamp '" + dateFormat.format(date) + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
return "to_date('" + date.toString() + "', 'yyyy-mm-dd')";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
postgresql {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "integer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
return "char(1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "bigint";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "real";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "double precision";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric(" + size + "," + precision + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "varchar(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "char(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "bytea";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "timestamp(3)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
return "nextval('" + sequenceName + "')";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
return "select nextval('" + sequenceName + "')";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
return "drop sequence " + dbtestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
return cycle ? " cycle" : " no cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "date_trunc('milliseconds',localtimestamp)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
return " cache " + nbrValuesToCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return "'" + dateFormat.format(date) + " GMT'::timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
return "'" + date.toString() + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hsqldb {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "integer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
return "char(1)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "bigint";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "double";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "double";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric(" + size + "," + precision + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "varchar(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "char(" + length + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "clob(2G)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "blob(2G)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "timestamp(3)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
return "next value for " + sequenceName + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
return "select " + sequenceNextVal(sequenceName) + fromAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
return "drop sequence if exists " + dbtestSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
return cycle ? " cycle" : " no cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return " from (values(0))";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "localtimestamp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000XXX");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return "cast(timestamp '" + dateFormat.format(date) + "' as timestamp without time zone)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
return "'" + date.toString() + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
return " as bigint";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bigquery {
|
||||||
|
@Override
|
||||||
|
public boolean isNormalizedUpperCase() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeInteger() {
|
||||||
|
return "int64";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBoolean() {
|
||||||
|
// BigQuery has a native boolean type, but we're not trying to use it
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLong() {
|
||||||
|
return "int64";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeFloat() {
|
||||||
|
return "float64";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDouble() {
|
||||||
|
return "float64";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBigDecimal(int size, int precision) {
|
||||||
|
return "numeric";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringVar(int length) {
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeStringFixed(int length) {
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeClob() {
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeBlob() {
|
||||||
|
return "bytes";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeDate() {
|
||||||
|
return "datetime";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeLocalDate() {
|
||||||
|
return "date";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceNextVal(String sequenceName) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceSelectNextVal(String sequenceName) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceDrop(String dbtestSeq) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInsertReturning() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dbTimeMillis() {
|
||||||
|
return "current_timestamp()";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCacheClause(int nbrValuesToCache) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOrderClause(boolean order) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceCycleClause(boolean cycle) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String fromAny() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dateAsSqlFunction(Date date, Calendar calendar) {
|
||||||
|
// Construct a datetime literal
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
|
||||||
|
dateFormat.setCalendar(calendar);
|
||||||
|
return String.format("datetime '%s'", dateFormat.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String localDateAsSqlFunction(Date date) {
|
||||||
|
// Construct a datetime literal
|
||||||
|
return String.format("datetime '%s'", date.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String sequenceOptions() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoCommitOnly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Flavor fromJdbcUrl(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new DatabaseException("url must not be null");
|
||||||
|
}
|
||||||
|
if (url.startsWith("jdbc:postgresql:")) {
|
||||||
|
return postgresql;
|
||||||
|
} else if (url.startsWith("jdbc:oracle:")) {
|
||||||
|
return oracle;
|
||||||
|
} else if (url.startsWith("jdbc:sqlserver:")) {
|
||||||
|
return sqlserver;
|
||||||
|
} else if (url.startsWith("jdbc:hsqldb:")) {
|
||||||
|
return hsqldb;
|
||||||
|
} else if (url.startsWith("jdbc:derby:")) {
|
||||||
|
return derby;
|
||||||
|
} else if (url.startsWith("jdbc:bigquery:")) {
|
||||||
|
return bigquery;
|
||||||
|
} else {
|
||||||
|
throw new DatabaseException("Cannot determine database flavor from url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String driverForJdbcUrl(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
throw new DatabaseException("url must not be null");
|
||||||
|
}
|
||||||
|
if (url.startsWith("jdbc:postgresql:")) {
|
||||||
|
return "org.postgresql.Driver";
|
||||||
|
} else if (url.startsWith("jdbc:oracle:")) {
|
||||||
|
return "oracle.jdbc.OracleDriver";
|
||||||
|
} else if (url.startsWith("jdbc:sqlserver:")) {
|
||||||
|
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
|
||||||
|
} else if (url.startsWith("jdbc:hsqldb:")) {
|
||||||
|
return "org.hsqldb.jdbc.JDBCDriver";
|
||||||
|
} else if (url.startsWith("jdbc:derby:")) {
|
||||||
|
return "org.apache.derby.jdbc.EmbeddedDriver";
|
||||||
|
} else if (url.startsWith("jdbc:bigquery:")) {
|
||||||
|
return "com.simba.googlebigquery.jdbc42.Driver";
|
||||||
|
} else {
|
||||||
|
throw new DatabaseException("Cannot determine database driver class from url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if DB normalizes to upper case names for ids like tables and columns
|
||||||
|
// See https://github.com/ontop/ontop/wiki/Case-sensitivity-for-SQL-identifiers
|
||||||
|
public abstract boolean isNormalizedUpperCase();
|
||||||
|
|
||||||
|
public abstract String typeInteger();
|
||||||
|
|
||||||
|
public abstract String typeBoolean();
|
||||||
|
|
||||||
|
public abstract String typeLong();
|
||||||
|
|
||||||
|
public abstract String typeFloat();
|
||||||
|
|
||||||
|
public abstract String typeDouble();
|
||||||
|
|
||||||
|
public abstract String typeBigDecimal(int size, int precision);
|
||||||
|
|
||||||
|
public abstract String typeStringVar(int length);
|
||||||
|
|
||||||
|
public abstract String typeStringFixed(int length);
|
||||||
|
|
||||||
|
public abstract String typeClob();
|
||||||
|
|
||||||
|
public abstract String typeBlob();
|
||||||
|
|
||||||
|
public abstract String typeDate();
|
||||||
|
|
||||||
|
public abstract String typeLocalDate();
|
||||||
|
|
||||||
|
public abstract boolean useStringForClob();
|
||||||
|
|
||||||
|
public abstract boolean useBytesForBlob();
|
||||||
|
|
||||||
|
public abstract String sequenceNextVal(String sequenceName);
|
||||||
|
|
||||||
|
public abstract String sequenceSelectNextVal(String sequenceName);
|
||||||
|
|
||||||
|
public abstract String sequenceDrop(String dbtestSeq);
|
||||||
|
|
||||||
|
public abstract boolean supportsInsertReturning();
|
||||||
|
|
||||||
|
public abstract String dbTimeMillis();
|
||||||
|
|
||||||
|
public abstract String sequenceCacheClause(int nbrValuesToCache);
|
||||||
|
|
||||||
|
public abstract String sequenceOrderClause(boolean order);
|
||||||
|
|
||||||
|
public abstract String sequenceCycleClause(boolean cycle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate what should follow a constant select statement. For example, "select 1"
|
||||||
|
* works on some databases, while Oracle requires "select 1 from dual". For Oracle
|
||||||
|
* this function should return " from dual" (including the leading space).
|
||||||
|
*/
|
||||||
|
public abstract String fromAny();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a SQL function representing the specified date. For example, in PostgreSQL this
|
||||||
|
* looks like "'1970-01-02 02:17:36.789000 GMT'::timestamp".
|
||||||
|
*/
|
||||||
|
public abstract String dateAsSqlFunction(Date date, Calendar calendar);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a SQL function representing the specified date without time.
|
||||||
|
*/
|
||||||
|
public abstract String localDateAsSqlFunction(Date date);
|
||||||
|
|
||||||
|
public abstract String sequenceOptions();
|
||||||
|
|
||||||
|
public abstract boolean autoCommitOnly();
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import org.xbib.jdbc.query.util.RewriteArg;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience class to allow use of (:mylabel) for SQL parameters in addition to
|
||||||
|
* positional (?) parameters. This doesn't do any smart parsing of the SQL, it is just
|
||||||
|
* looking for ':' and '?' characters. If the SQL needs to include an actual ':' or '?'
|
||||||
|
* character, use two of them ('::' or '??'), and they will be replaced with a
|
||||||
|
* single ':' or '?'.
|
||||||
|
*/
|
||||||
|
public class MixedParameterSql {
|
||||||
|
|
||||||
|
private final String sqlToExecute;
|
||||||
|
|
||||||
|
private final Object[] args;
|
||||||
|
|
||||||
|
public MixedParameterSql(String sql, List<Object> positionalArgs, Map<String, Object> nameToArg) {
|
||||||
|
if (positionalArgs == null) {
|
||||||
|
positionalArgs = new ArrayList<>();
|
||||||
|
}
|
||||||
|
if (nameToArg == null) {
|
||||||
|
nameToArg = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder newSql = new StringBuilder(sql.length());
|
||||||
|
List<String> argNamesList = new ArrayList<>();
|
||||||
|
List<String> rewrittenArgs = new ArrayList<>();
|
||||||
|
List<Object> argsList = new ArrayList<>();
|
||||||
|
int searchIndex = 0;
|
||||||
|
int currentPositionalArg = 0;
|
||||||
|
while (searchIndex < sql.length()) {
|
||||||
|
int nextColonIndex = sql.indexOf(':', searchIndex);
|
||||||
|
int nextQmIndex = sql.indexOf('?', searchIndex);
|
||||||
|
|
||||||
|
if (nextColonIndex < 0 && nextQmIndex < 0) {
|
||||||
|
newSql.append(sql.substring(searchIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextColonIndex >= 0 && (nextQmIndex == -1 || nextColonIndex < nextQmIndex)) {
|
||||||
|
// The next parameter we found is a named parameter (":foo")
|
||||||
|
if (nextColonIndex > sql.length() - 2) {
|
||||||
|
// Probably illegal sql, but handle boundary condition
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow :: as escape for :
|
||||||
|
if (sql.charAt(nextColonIndex + 1) == ':') {
|
||||||
|
newSql.append(sql, searchIndex, nextColonIndex + 1);
|
||||||
|
searchIndex = nextColonIndex + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int endOfNameIndex = nextColonIndex + 1;
|
||||||
|
while (endOfNameIndex < sql.length() && Character.isJavaIdentifierPart(sql.charAt(endOfNameIndex))) {
|
||||||
|
endOfNameIndex++;
|
||||||
|
}
|
||||||
|
newSql.append(sql, searchIndex, nextColonIndex);
|
||||||
|
String paramName = sql.substring(nextColonIndex + 1, endOfNameIndex);
|
||||||
|
boolean secretParam = paramName.startsWith("secret");
|
||||||
|
Object arg = nameToArg.get(paramName);
|
||||||
|
if (arg instanceof RewriteArg) {
|
||||||
|
newSql.append(((RewriteArg) arg).getSql());
|
||||||
|
rewrittenArgs.add(paramName);
|
||||||
|
} else {
|
||||||
|
newSql.append('?');
|
||||||
|
if (nameToArg.containsKey(paramName)) {
|
||||||
|
argsList.add(secretParam ? new SecretArg(arg) : arg);
|
||||||
|
} else {
|
||||||
|
throw new DatabaseException("The SQL requires parameter ':" + paramName + "' but no value was provided");
|
||||||
|
}
|
||||||
|
argNamesList.add(paramName);
|
||||||
|
}
|
||||||
|
searchIndex = endOfNameIndex;
|
||||||
|
} else {
|
||||||
|
// The next parameter we found is a positional parameter ("?")
|
||||||
|
|
||||||
|
// Allow ?? as escape for ?
|
||||||
|
if (nextQmIndex < sql.length() - 1 && sql.charAt(nextQmIndex + 1) == '?') {
|
||||||
|
newSql.append(sql, searchIndex, nextQmIndex + 1);
|
||||||
|
searchIndex = nextQmIndex + 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSql.append(sql, searchIndex, nextQmIndex);
|
||||||
|
if (currentPositionalArg >= positionalArgs.size()) {
|
||||||
|
throw new DatabaseException("Not enough positional parameters (" + positionalArgs.size() + ") were provided");
|
||||||
|
}
|
||||||
|
if (positionalArgs.get(currentPositionalArg) instanceof RewriteArg) {
|
||||||
|
newSql.append(((RewriteArg) positionalArgs.get(currentPositionalArg)).getSql());
|
||||||
|
} else {
|
||||||
|
newSql.append('?');
|
||||||
|
argsList.add(positionalArgs.get(currentPositionalArg));
|
||||||
|
}
|
||||||
|
currentPositionalArg++;
|
||||||
|
searchIndex = nextQmIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sqlToExecute = newSql.toString();
|
||||||
|
args = argsList.toArray(new Object[argsList.size()]);
|
||||||
|
|
||||||
|
// Sanity check number of arguments to provide a better error message
|
||||||
|
if (currentPositionalArg != positionalArgs.size()) {
|
||||||
|
throw new DatabaseException("Wrong number of positional parameters were provided (expected: "
|
||||||
|
+ currentPositionalArg + ", actual: " + positionalArgs.size() + ")");
|
||||||
|
}
|
||||||
|
if (nameToArg.size() > args.length - Math.max(0, positionalArgs.size() - 1) + rewrittenArgs.size()) {
|
||||||
|
Set<String> unusedNames = new HashSet<>(nameToArg.keySet());
|
||||||
|
unusedNames.removeAll(argNamesList);
|
||||||
|
unusedNames.removeAll(rewrittenArgs);
|
||||||
|
if (!unusedNames.isEmpty()) {
|
||||||
|
throw new DatabaseException("These named parameters do not exist in the query: " + unusedNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSqlToExecute() {
|
||||||
|
return sqlToExecute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
130
jdbc-query/src/main/java/org/xbib/jdbc/query/Options.java
Normal file
130
jdbc-query/src/main/java/org/xbib/jdbc/query/Options.java
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control various optional behavior for the database interactions.
|
||||||
|
*/
|
||||||
|
public interface Options {
|
||||||
|
/**
|
||||||
|
* Control whether the Database object will allow calls to commitNow()
|
||||||
|
* and rollbackNow(). By default it will throw exceptions if you try to
|
||||||
|
* call those.
|
||||||
|
*/
|
||||||
|
boolean allowTransactionControl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful for testing code that explicitly controls transactions, and you
|
||||||
|
* don't really want it to commit/rollback. Disabled by default, meaning
|
||||||
|
* calls will be allowed or throw exceptions depending on allowTransctionControl().
|
||||||
|
* The value of allowTranscationControl() has no affect if this returns true.
|
||||||
|
*/
|
||||||
|
boolean ignoreTransactionControl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control whether the Database object will allow calls to underlyingConnection().
|
||||||
|
* By default that method will throw an exception.
|
||||||
|
*/
|
||||||
|
boolean allowConnectionAccess();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is false, log messages will look something like:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ...select a from b where c=?
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* If this is true, log messages will look something like:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ...select a from b where c=?|select a from b where c='abc'
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return true if parameter values should be logged along with SQL, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isLogParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, text of the SQL and possibly parameter values (depending on @{#isLogParameters()})
|
||||||
|
* will be included in exception messages. This can be very helpful for debugging, but poses
|
||||||
|
* some disclosure risks.
|
||||||
|
*
|
||||||
|
* @return true to add possibly sensitive data in exception messages, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isDetailedExceptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In cases where exceptions are thrown, use this method to provide a common
|
||||||
|
* code that will be included in the exception message and the log message
|
||||||
|
* so they can be searched and correlated later.
|
||||||
|
*
|
||||||
|
* @return an arbitrary, fairly unique, speakable over the phone, without whitespace
|
||||||
|
*/
|
||||||
|
String generateErrorCode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether to use the Blob functionality of the underlying database driver,
|
||||||
|
* or whether to use setBytes() methods instead. Using Blobs is preferred, but is not
|
||||||
|
* supported by all drivers.
|
||||||
|
*
|
||||||
|
* <p>The default behavior of this method is to delegate to flavor().useBytesForBlob(),
|
||||||
|
* but it is provided on this interface so the behavior can be controlled.
|
||||||
|
*
|
||||||
|
* @return true to avoid using Blob functionality, false otherwise
|
||||||
|
*/
|
||||||
|
boolean useBytesForBlob();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether to use the Clob functionality of the underlying database driver,
|
||||||
|
* or whether to use setString() methods instead. Using Clobs is preferred, but is not
|
||||||
|
* supported by all drivers.
|
||||||
|
*
|
||||||
|
* <p>The default behavior of this method is to delegate to flavor().useStringForClob(),
|
||||||
|
* but it is provided on this interface so the behavior can be controlled.
|
||||||
|
*
|
||||||
|
* @return true to avoid using Clob functionality, false otherwise
|
||||||
|
*/
|
||||||
|
boolean useStringForClob();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access compatibility information for the underlying database. The
|
||||||
|
* Flavor class enumerates the known databases and tries to smooth over
|
||||||
|
* some of the variations in features and syntax.
|
||||||
|
*/
|
||||||
|
Flavor flavor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value returned by this method will be used for argDateNowPerApp() calls. It
|
||||||
|
* may also be used for argDateNowPerDb() calls if you have enabled that.
|
||||||
|
*/
|
||||||
|
Date currentDate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is
|
||||||
|
* useful for testing purposes as you can use OptionsOverride to provide your
|
||||||
|
* own system clock that will be used for time travel.
|
||||||
|
*/
|
||||||
|
boolean useDatePerAppOnly();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This calendar will be used for conversions when storing and retrieving timestamps
|
||||||
|
* from the database. By default this is the JVM default with TimeZone explicitly set
|
||||||
|
* to GMT (so timestamps will be stored in the database as GMT).
|
||||||
|
*
|
||||||
|
* <p>It is strongly recommended to always run your database in GMT timezone, and
|
||||||
|
* leave this set to the default.</p>
|
||||||
|
*
|
||||||
|
* <p>Behavior in releases 1.3 and prior was to use the JVM default TimeZone, and
|
||||||
|
* this was not configurable.</p>
|
||||||
|
*/
|
||||||
|
Calendar calendarForTimestamps();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of characters to print in debug SQL for a given String type
|
||||||
|
* insert/update/query parameter. If it exceeds this length, the parameter value
|
||||||
|
* will be truncated at the max and a "..." will be appended. Note this affects
|
||||||
|
* both {@code argString()} and {@code argClobString()} methods.
|
||||||
|
*/
|
||||||
|
int maxStringLengthParam();
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control various optional behavior for the database interactions.
|
||||||
|
*/
|
||||||
|
public class OptionsDefault implements Options {
|
||||||
|
|
||||||
|
private final Flavor flavor;
|
||||||
|
|
||||||
|
public OptionsDefault(Flavor flavor) {
|
||||||
|
this.flavor = flavor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowTransactionControl() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean ignoreTransactionControl() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowConnectionAccess() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLogParameters() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDetailedExceptions() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateErrorCode() {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:H:m:s");
|
||||||
|
return sdf.format(new Date()) + "-" + Math.round(Math.random() * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return flavor().useBytesForBlob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return flavor().useStringForClob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flavor flavor() {
|
||||||
|
return flavor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date currentDate() {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useDatePerAppOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar calendarForTimestamps() {
|
||||||
|
return Calendar.getInstance(TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxStringLengthParam() {
|
||||||
|
return 4000;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for selectively overriding another Options object.
|
||||||
|
*/
|
||||||
|
public class OptionsOverride implements Options {
|
||||||
|
|
||||||
|
private Options parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap another {@code Options} and defer to it for anything we choose not
|
||||||
|
* to override.
|
||||||
|
*/
|
||||||
|
public OptionsOverride(Options parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defer to OptionsDefault for anything that is not specified, and use postgresql flavor.
|
||||||
|
*/
|
||||||
|
public OptionsOverride() {
|
||||||
|
parent = new OptionsDefault(Flavor.postgresql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defer to OptionsDefault for anything that is not specified, using the specified flavor.
|
||||||
|
*/
|
||||||
|
public OptionsOverride(Flavor flavor) {
|
||||||
|
parent = new OptionsDefault(flavor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(Options parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionsOverride withParent(Options parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowTransactionControl() {
|
||||||
|
return parent.allowTransactionControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean ignoreTransactionControl() {
|
||||||
|
return parent.ignoreTransactionControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowConnectionAccess() {
|
||||||
|
return parent.allowConnectionAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLogParameters() {
|
||||||
|
return parent.isLogParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDetailedExceptions() {
|
||||||
|
return parent.isDetailedExceptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateErrorCode() {
|
||||||
|
return parent.generateErrorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useBytesForBlob() {
|
||||||
|
return parent.useBytesForBlob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useStringForClob() {
|
||||||
|
return parent.useStringForClob();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flavor flavor() {
|
||||||
|
return parent.flavor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date currentDate() {
|
||||||
|
return parent.currentDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useDatePerAppOnly() {
|
||||||
|
return parent.useDatePerAppOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar calendarForTimestamps() {
|
||||||
|
return parent.calendarForTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxStringLengthParam() {
|
||||||
|
return parent.maxStringLengthParam();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a query is interrupted because a timeout was exceeded or it was
|
||||||
|
* explicitly cancelled.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class QueryTimedOutException extends DatabaseException {
|
||||||
|
|
||||||
|
public QueryTimedOutException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
435
jdbc-query/src/main/java/org/xbib/jdbc/query/Row.java
Normal file
435
jdbc-query/src/main/java/org/xbib/jdbc/query/Row.java
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for reading results from a database query.
|
||||||
|
*/
|
||||||
|
public interface Row {
|
||||||
|
/**
|
||||||
|
* Obtain the names of the columns in the database. You probably want to
|
||||||
|
* avoid this method if possible, as the way column names are handled varies
|
||||||
|
* by database and driver. For example, Derby and Oracle normally convert
|
||||||
|
* column names to uppercase, while PostgreSQL normally converts column
|
||||||
|
* names to lowercase. If you do use this method, you might want to either
|
||||||
|
* call toUppercase()/toLowercase() or ensure the SQL explicitly specifies
|
||||||
|
* parameters with AS "FOO" (including quotes) to ensure your desired name
|
||||||
|
* will be honored.
|
||||||
|
*/
|
||||||
|
String[] getColumnLabels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get raw access to the underlying JDBC metadata.
|
||||||
|
*/
|
||||||
|
ResultSetMetaData getMetadata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to {@code Boolean} or {@code null} as appropriate.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* <p>If you call one of the methods using an explicit column index or column name before
|
||||||
|
* calling this method, it will pick up at the next column following the explicit one.
|
||||||
|
* For example:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* getX(); // column 1
|
||||||
|
* getX(5); // or getX("foo") if foo is column 5
|
||||||
|
* getX(); // column 6
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return true if the value was "Y", false if it was "N", or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
Boolean getBooleanOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to {@code Boolean} or {@code null} as appropriate.
|
||||||
|
*
|
||||||
|
* @param columnOneBased column number to read (1 is the first column)
|
||||||
|
* @return true if the value was "Y", false if it was "N", or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
Boolean getBooleanOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to {@code Boolean} or {@code null} as appropriate.
|
||||||
|
*
|
||||||
|
* @param columnName SQL alias of the column to read (use all lowercase)
|
||||||
|
* @return true if the value was "Y", false if it was "N", or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
Boolean getBooleanOrNull(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* <p>If you call one of the methods using an explicit column index or column name before
|
||||||
|
* calling this method, it will pick up at the next column following the explicit one.
|
||||||
|
* For example:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* getX(); // column 1
|
||||||
|
* getX(5); // or getX("foo") if foo is column 5
|
||||||
|
* getX(); // column 6
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return true if the value was "Y", false if it was either "N" or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrFalse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* @param columnOneBased column number to read (1 is the first column)
|
||||||
|
* @return true if the value was "Y", false if it was either "N" or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrFalse(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code false}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* @param columnName SQL alias of the column to read (use all lowercase)
|
||||||
|
* @return true if the value was "Y", false if it was either "N" or null
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrFalse(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* <p>If you call one of the methods using an explicit column index or column name before
|
||||||
|
* calling this method, it will pick up at the next column following the explicit one.
|
||||||
|
* For example:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* getX(); // column 1
|
||||||
|
* getX(5); // or getX("foo") if foo is column 5
|
||||||
|
* getX(); // column 6
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return true if the value was either "Y" or null, false if it was "N"
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrTrue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* @param columnOneBased column number to read (1 is the first column)
|
||||||
|
* @return true if the value was either "Y" or null, false if it was "N"
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrTrue(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean values are represented as strings {@code "Y"} or {@code "N"} in the database,
|
||||||
|
* typically in a {@code CHAR(1)} column. This reads the value and converts it
|
||||||
|
* to a {@code boolean}. If the value is {@code null}, it will be converted to {@code true}.
|
||||||
|
*
|
||||||
|
* <p>This is a short-hand method that reads columns in order, starting
|
||||||
|
* with the first, and automatically incrementing the column index.</p>
|
||||||
|
*
|
||||||
|
* @param columnName SQL alias of the column to read (use all lowercase)
|
||||||
|
* @return true if the value was either "Y" or null, false if it was "N"
|
||||||
|
* @throws DatabaseException if the value was something other than Y, N, or null
|
||||||
|
*/
|
||||||
|
boolean getBooleanOrTrue(String columnName);
|
||||||
|
|
||||||
|
Integer getIntegerOrNull();
|
||||||
|
|
||||||
|
Integer getIntegerOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
Integer getIntegerOrNull(String columnName);
|
||||||
|
|
||||||
|
int getIntegerOrZero();
|
||||||
|
|
||||||
|
int getIntegerOrZero(int columnOneBased);
|
||||||
|
|
||||||
|
int getIntegerOrZero(String columnName);
|
||||||
|
|
||||||
|
Long getLongOrNull();
|
||||||
|
|
||||||
|
Long getLongOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
Long getLongOrNull(String columnName);
|
||||||
|
|
||||||
|
long getLongOrZero();
|
||||||
|
|
||||||
|
long getLongOrZero(int columnOneBased);
|
||||||
|
|
||||||
|
long getLongOrZero(String columnName);
|
||||||
|
|
||||||
|
Float getFloatOrNull();
|
||||||
|
|
||||||
|
Float getFloatOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
Float getFloatOrNull(String columnName);
|
||||||
|
|
||||||
|
float getFloatOrZero();
|
||||||
|
|
||||||
|
float getFloatOrZero(int columnOneBased);
|
||||||
|
|
||||||
|
float getFloatOrZero(String columnName);
|
||||||
|
|
||||||
|
Double getDoubleOrNull();
|
||||||
|
|
||||||
|
Double getDoubleOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
Double getDoubleOrNull(String columnName);
|
||||||
|
|
||||||
|
double getDoubleOrZero();
|
||||||
|
|
||||||
|
double getDoubleOrZero(int columnOneBased);
|
||||||
|
|
||||||
|
double getDoubleOrZero(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note this method attempts to correct for "artifical" scale due to the database
|
||||||
|
* representation. Some databases will pad the number out to "full precision". This
|
||||||
|
* method tries to reduce scale if there is zero padding to the right of the decimal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrNull();
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrNull(String columnName);
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrZero();
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrZero(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
BigDecimal getBigDecimalOrZero(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrNull(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrEmpty(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getStringOrEmpty(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null; never returns the empty string
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrNull(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrEmpty(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or the empty string if it is SQL null; never returns null
|
||||||
|
*/
|
||||||
|
|
||||||
|
String getClobStringOrEmpty(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or null if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrNull(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or a StringReader containing the empty string if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or a StringReader containing the empty string if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrEmpty(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value, or a StringReader containing the empty string if it is SQL null
|
||||||
|
*/
|
||||||
|
|
||||||
|
Reader getClobReaderOrEmpty(String columnName);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrNull();
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrNull(String columnName);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrZeroLen();
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrZeroLen(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] getBlobBytesOrZeroLen(String columnName);
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrNull();
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrNull(String columnName);
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrEmpty();
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrEmpty(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
InputStream getBlobInputStreamOrEmpty(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the millisecond precision Date, which should be represented as a TIMESTAMP
|
||||||
|
* in the database. The nanoseconds are truncated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Date getDateOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the millisecond precision Date, which should be represented as a TIMESTAMP
|
||||||
|
* in the database. The nanoseconds are truncated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Date getDateOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
|
||||||
|
Date getDateOrNull(String columnName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve column as LocalDate, .i.e, date with no time.
|
||||||
|
*
|
||||||
|
* @return LocalDate of the database column value
|
||||||
|
*/
|
||||||
|
|
||||||
|
LocalDate getLocalDateOrNull();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Date field, with no timestamp
|
||||||
|
*
|
||||||
|
* @param columnOneBased column number starting at 1, not 0
|
||||||
|
* @return LocalDate of the column value
|
||||||
|
*/
|
||||||
|
|
||||||
|
LocalDate getLocalDateOrNull(int columnOneBased);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Date field, with no timestamp
|
||||||
|
*
|
||||||
|
* @param columnName column name to retrieve
|
||||||
|
* @return LocalDate of the column value
|
||||||
|
*/
|
||||||
|
|
||||||
|
LocalDate getLocalDateOrNull(String columnName);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe callback to read query results.
|
||||||
|
*/
|
||||||
|
public interface RowHandler<T> {
|
||||||
|
T process(Row r) throws Exception;
|
||||||
|
}
|
8
jdbc-query/src/main/java/org/xbib/jdbc/query/Rows.java
Normal file
8
jdbc-query/src/main/java/org/xbib/jdbc/query/Rows.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for reading results from a database query.
|
||||||
|
*/
|
||||||
|
public interface Rows extends Row {
|
||||||
|
boolean next();
|
||||||
|
}
|
835
jdbc-query/src/main/java/org/xbib/jdbc/query/RowsAdaptor.java
Normal file
835
jdbc-query/src/main/java/org/xbib/jdbc/query/RowsAdaptor.java
Normal file
|
@ -0,0 +1,835 @@
|
||||||
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely wrap a ResultSet and provide access to the data it contains.
|
||||||
|
*/
|
||||||
|
class RowsAdaptor implements Rows {
|
||||||
|
|
||||||
|
private final ResultSet rs;
|
||||||
|
|
||||||
|
private final Options options;
|
||||||
|
|
||||||
|
private int column = 1;
|
||||||
|
|
||||||
|
public RowsAdaptor(ResultSet rs, Options options) {
|
||||||
|
this.rs = rs;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean next() {
|
||||||
|
try {
|
||||||
|
column = 1;
|
||||||
|
return rs.next();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getColumnLabels() {
|
||||||
|
try {
|
||||||
|
ResultSetMetaData metaData = rs.getMetaData();
|
||||||
|
String[] names = new String[metaData.getColumnCount()];
|
||||||
|
for (int i = 0; i < names.length; i++) {
|
||||||
|
names[i] = metaData.getColumnLabel(i + 1);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException("Unable to retrieve metadata from ResultSet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResultSetMetaData getMetadata() {
|
||||||
|
try {
|
||||||
|
return rs.getMetaData();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException("Unable to retrieve metadata from ResultSet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getBooleanOrNull() {
|
||||||
|
return getBooleanOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getBooleanOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toBoolean(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getBooleanOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toBoolean(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrFalse() {
|
||||||
|
return getBooleanOrFalse(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrFalse(int columnOneBased) {
|
||||||
|
Boolean result = getBooleanOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = Boolean.FALSE;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrFalse(String columnName) {
|
||||||
|
Boolean result = getBooleanOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = Boolean.FALSE;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrTrue() {
|
||||||
|
return getBooleanOrTrue(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrTrue(int columnOneBased) {
|
||||||
|
Boolean result = getBooleanOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = Boolean.TRUE;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getBooleanOrTrue(String columnName) {
|
||||||
|
Boolean result = getBooleanOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = Boolean.TRUE;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getIntegerOrNull() {
|
||||||
|
return getIntegerOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getIntegerOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toInteger(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getIntegerOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toInteger(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntegerOrZero() {
|
||||||
|
return getIntegerOrZero(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntegerOrZero(int columnOneBased) {
|
||||||
|
Integer result = getIntegerOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntegerOrZero(String columnName) {
|
||||||
|
Integer result = getIntegerOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLongOrNull() {
|
||||||
|
return getLongOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLongOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toLong(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLongOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toLong(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLongOrZero() {
|
||||||
|
return getLongOrZero(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLongOrZero(int columnOneBased) {
|
||||||
|
Long result = getLongOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0L;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLongOrZero(String columnName) {
|
||||||
|
Long result = getLongOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0L;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getFloatOrNull() {
|
||||||
|
return getFloatOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getFloatOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toFloat(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getFloatOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toFloat(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloatOrZero() {
|
||||||
|
return getFloatOrZero(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloatOrZero(int columnOneBased) {
|
||||||
|
Float result = getFloatOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0f;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getFloatOrZero(String columnName) {
|
||||||
|
Float result = getFloatOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0f;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getDoubleOrNull() {
|
||||||
|
return getDoubleOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getDoubleOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toDouble(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getDoubleOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toDouble(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDoubleOrZero() {
|
||||||
|
return getDoubleOrZero(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDoubleOrZero(int columnOneBased) {
|
||||||
|
Double result = getDoubleOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0d;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getDoubleOrZero(String columnName) {
|
||||||
|
Double result = getDoubleOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = 0d;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrNull() {
|
||||||
|
return getBigDecimalOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toBigDecimal(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toBigDecimal(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrZero() {
|
||||||
|
return getBigDecimalOrZero(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrZero(int columnOneBased) {
|
||||||
|
BigDecimal result = getBigDecimalOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getBigDecimalOrZero(String columnName) {
|
||||||
|
BigDecimal result = getBigDecimalOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrNull() {
|
||||||
|
return getStringOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
String result = rs.getString(columnOneBased);
|
||||||
|
if (result != null && result.length() == 0) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
String result = rs.getString(columnName);
|
||||||
|
if (result != null && result.length() == 0) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrEmpty() {
|
||||||
|
return getStringOrEmpty(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrEmpty(int columnOneBased) {
|
||||||
|
String result = getStringOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStringOrEmpty(String columnName) {
|
||||||
|
String result = getStringOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrNull() {
|
||||||
|
return getClobStringOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
String result = rs.getString(columnOneBased);
|
||||||
|
if (result != null && result.length() == 0) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
String result = rs.getString(columnName);
|
||||||
|
if (result != null && result.length() == 0) {
|
||||||
|
result = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrEmpty() {
|
||||||
|
return getClobStringOrEmpty(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrEmpty(int columnOneBased) {
|
||||||
|
String result = getClobStringOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClobStringOrEmpty(String columnName) {
|
||||||
|
String result = getClobStringOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = "";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrNull() {
|
||||||
|
return getClobReaderOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return rs.getCharacterStream(columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return rs.getCharacterStream(columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrEmpty() {
|
||||||
|
return getClobReaderOrEmpty(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrEmpty(int columnOneBased) {
|
||||||
|
Reader result = getClobReaderOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = new StringReader("");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Reader getClobReaderOrEmpty(String columnName) {
|
||||||
|
Reader result = getClobReaderOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = new StringReader("");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrNull() {
|
||||||
|
return getBlobBytesOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return rs.getBytes(columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return rs.getBytes(columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrZeroLen() {
|
||||||
|
return getBlobBytesOrZeroLen(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrZeroLen(int columnOneBased) {
|
||||||
|
byte[] result = getBlobBytesOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = new byte[0];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBlobBytesOrZeroLen(String columnName) {
|
||||||
|
byte[] result = getBlobBytesOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = new byte[0];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrNull() {
|
||||||
|
return getBlobInputStreamOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return rs.getBinaryStream(columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return rs.getBinaryStream(columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrEmpty() {
|
||||||
|
return getBlobInputStreamOrEmpty(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrEmpty(int columnOneBased) {
|
||||||
|
InputStream result = getBlobInputStreamOrNull(columnOneBased);
|
||||||
|
if (result == null) {
|
||||||
|
result = new ByteArrayInputStream(new byte[0]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getBlobInputStreamOrEmpty(String columnName) {
|
||||||
|
InputStream result = getBlobInputStreamOrNull(columnName);
|
||||||
|
if (result == null) {
|
||||||
|
result = new ByteArrayInputStream(new byte[0]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDateOrNull() {
|
||||||
|
return getDateOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDateOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toDate(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getDateOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toDate(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDate getLocalDateOrNull() {
|
||||||
|
return getLocalDateOrNull(column++);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDate getLocalDateOrNull(int columnOneBased) {
|
||||||
|
try {
|
||||||
|
column = columnOneBased + 1;
|
||||||
|
return toLocalDate(rs, columnOneBased);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalDate getLocalDateOrNull(String columnName) {
|
||||||
|
try {
|
||||||
|
column = rs.findColumn(columnName) + 1;
|
||||||
|
return toLocalDate(rs, columnName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure the Timestamp will return getTime() accurate to the millisecond
|
||||||
|
* (if possible) and truncate away nanoseconds.
|
||||||
|
*/
|
||||||
|
private Date timestampToDate(Timestamp ts) {
|
||||||
|
long millis = ts.getTime();
|
||||||
|
int nanos = ts.getNanos();
|
||||||
|
return new Date(millis / 1000 * 1000 + nanos / 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date toDate(ResultSet rs, int col) throws SQLException {
|
||||||
|
Timestamp val = rs.getTimestamp(col, options.calendarForTimestamps());
|
||||||
|
return val == null ? null : timestampToDate(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date toDate(ResultSet rs, String col) throws SQLException {
|
||||||
|
Timestamp val = rs.getTimestamp(col, options.calendarForTimestamps());
|
||||||
|
return val == null ? null : timestampToDate(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate toLocalDate(ResultSet rs, int col) throws SQLException {
|
||||||
|
java.sql.Date val = rs.getDate(col);
|
||||||
|
return val == null ? null : val.toLocalDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate toLocalDate(ResultSet rs, String col) throws SQLException {
|
||||||
|
java.sql.Date val = rs.getDate(col);
|
||||||
|
return val == null ? null : val.toLocalDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean toBoolean(ResultSet rs, int col) throws SQLException {
|
||||||
|
String val = rs.getString(col);
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val.equals("Y") || val.equals("1")) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
} else if (val.equals("N") || val.equals("0")) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
} else {
|
||||||
|
throw new DatabaseException("Reading boolean from column " + col + " but the value was not 'Y' or 'N'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean toBoolean(ResultSet rs, String col) throws SQLException {
|
||||||
|
String val = rs.getString(col);
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
|
} else if (val.equals("Y") || val.equals("1")) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
} else if (val.equals("N") || val.equals("0")) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
} else {
|
||||||
|
throw new DatabaseException("Reading boolean from column \"" + col + "\" but the value was not 'Y' or 'N'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer toInteger(ResultSet rs, int col) throws SQLException {
|
||||||
|
int val = rs.getInt(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer toInteger(ResultSet rs, String col) throws SQLException {
|
||||||
|
int val = rs.getInt(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long toLong(ResultSet rs, int col) throws SQLException {
|
||||||
|
long val = rs.getLong(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long toLong(ResultSet rs, String col) throws SQLException {
|
||||||
|
long val = rs.getLong(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Float toFloat(ResultSet rs, int col) throws SQLException {
|
||||||
|
float val = rs.getFloat(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Float toFloat(ResultSet rs, String col) throws SQLException {
|
||||||
|
float val = rs.getFloat(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double toDouble(ResultSet rs, int col) throws SQLException {
|
||||||
|
double val = rs.getDouble(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double toDouble(ResultSet rs, String col) throws SQLException {
|
||||||
|
double val = rs.getDouble(col);
|
||||||
|
return rs.wasNull() ? null : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal fixBigDecimal(BigDecimal val) {
|
||||||
|
if (val.scale() > 0) {
|
||||||
|
val = val.stripTrailingZeros();
|
||||||
|
if (val.scale() < 0) {
|
||||||
|
val = val.setScale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal toBigDecimal(ResultSet rs, int col) throws SQLException {
|
||||||
|
BigDecimal val = rs.getBigDecimal(col);
|
||||||
|
return val == null ? null : fixBigDecimal(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal toBigDecimal(ResultSet rs, String col) throws SQLException {
|
||||||
|
BigDecimal val = rs.getBigDecimal(col);
|
||||||
|
return val == null ? null : fixBigDecimal(val);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue