initial commit of 42.7.4.0
This commit is contained in:
commit
698d755934
647 changed files with 131167 additions and 0 deletions
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/.settings
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.gradle
|
||||||
|
**/data
|
||||||
|
**/work
|
||||||
|
**/logs
|
||||||
|
**/.idea
|
||||||
|
**/target
|
||||||
|
**/out
|
||||||
|
**/build
|
||||||
|
.DS_Store
|
||||||
|
*.iml
|
||||||
|
*~
|
34
build.gradle
Normal file
34
build.gradle
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
plugins {
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'signing'
|
||||||
|
id "io.github.gradle-nexus.publish-plugin" version "2.0.0-rc-1"
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
gradleVersion = libs.versions.gradle.get()
|
||||||
|
distributionType = Wrapper.DistributionType.BIN
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
user = 'joerg'
|
||||||
|
name = 'pgjdbc'
|
||||||
|
description = 'Fork of pgjdbc for simplified build and Java 21+'
|
||||||
|
inceptionYear = '2024'
|
||||||
|
url = 'https://xbib.org/' + user + '/' + name
|
||||||
|
scmUrl = 'https://xbib.org/' + user + '/' + name
|
||||||
|
scmConnection = 'scm:git:git://xbib.org/' + user + '/' + name + '.git'
|
||||||
|
scmDeveloperConnection = 'scm:git:ssh://forgejo@xbib.org:' + user + '/' + name + '.git'
|
||||||
|
issueManagementSystem = 'Forgejo'
|
||||||
|
issueManagementUrl = ext.scmUrl + '/issues'
|
||||||
|
licenseName = 'The Apache License, Version 2.0'
|
||||||
|
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply from: rootProject.file('gradle/compile/java.gradle')
|
||||||
|
apply from: rootProject.file('gradle/test/junit5.gradle')
|
||||||
|
apply from: rootProject.file('gradle/repositories/maven.gradle')
|
||||||
|
apply from: rootProject.file('gradle/publish/maven.gradle')
|
||||||
|
}
|
||||||
|
apply from: rootProject.file('gradle/publish/sonatype.gradle')
|
||||||
|
apply from: rootProject.file('gradle/publish/forgejo.gradle')
|
4
gradle.properties
Normal file
4
gradle.properties
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
group = org.xbib.jdbc
|
||||||
|
name = pgjdbc
|
||||||
|
version = 42.7.4.0
|
||||||
|
|
30
gradle/compile/java.gradle
Normal file
30
gradle/compile/java.gradle
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
modularity.inferModulePath.set(true)
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes('Implementation-Version': project.version)
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.fork = true
|
||||||
|
options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US']
|
||||||
|
options.compilerArgs.add('-Xlint:all')
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Javadoc) {
|
||||||
|
options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
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')
|
||||||
|
}
|
||||||
|
project {
|
||||||
|
jdkName = '17'
|
||||||
|
languageLevel = '17'
|
||||||
|
vcs = 'Git'
|
||||||
|
}
|
||||||
|
}
|
16
gradle/publish/forgejo.gradle
Normal file
16
gradle/publish/forgejo.gradle
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
if (project.hasProperty('forgeJoToken')) {
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'https://xbib.org/api/packages/joerg/maven'
|
||||||
|
credentials(HttpHeaderCredentials) {
|
||||||
|
name = "Authorization"
|
||||||
|
value = "token ${project.property('forgeJoToken')}"
|
||||||
|
}
|
||||||
|
authentication {
|
||||||
|
header(HttpHeaderAuthentication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
gradle/publish/ivy.gradle
Normal file
27
gradle/publish/ivy.gradle
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
apply plugin: 'ivy-publish'
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
ivy {
|
||||||
|
url = "https://xbib.org/repo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publications {
|
||||||
|
ivy(IvyPublication) {
|
||||||
|
from components.java
|
||||||
|
descriptor {
|
||||||
|
license {
|
||||||
|
name = 'The Apache License, Version 2.0'
|
||||||
|
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
author {
|
||||||
|
name = 'Jörg Prante'
|
||||||
|
url = 'http://example.com/users/jane'
|
||||||
|
}
|
||||||
|
descriptor.description {
|
||||||
|
text = rootProject.ext.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
gradle/publish/maven.gradle
Normal file
51
gradle/publish/maven.gradle
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
"${project.name}"(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
pom {
|
||||||
|
artifactId = project.name
|
||||||
|
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://xbib.org/joerg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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."${project.name}"
|
||||||
|
}
|
||||||
|
}
|
12
gradle/publish/sonatype.gradle
Normal file
12
gradle/publish/sonatype.gradle
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword')) {
|
||||||
|
nexusPublishing {
|
||||||
|
repositories {
|
||||||
|
sonatype {
|
||||||
|
username = project.property('ossrhUsername')
|
||||||
|
password = project.property('ossrhPassword')
|
||||||
|
packageGroup = "org.xbib"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
gradle/quality/checkstyle.gradle
Normal file
19
gradle/quality/checkstyle.gradle
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
|
tasks.withType(Checkstyle) {
|
||||||
|
ignoreFailures = true
|
||||||
|
reports {
|
||||||
|
xml.getRequired().set(true)
|
||||||
|
html.getRequired().set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkstyle {
|
||||||
|
configFile = rootProject.file('gradle/quality/checkstyle.xml')
|
||||||
|
ignoreFailures = true
|
||||||
|
showViolations = true
|
||||||
|
checkstyleMain {
|
||||||
|
source = sourceSets.main.allSource
|
||||||
|
}
|
||||||
|
}
|
333
gradle/quality/checkstyle.xml
Normal file
333
gradle/quality/checkstyle.xml
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
|
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<!-- This is a checkstyle configuration file. For descriptions of
|
||||||
|
what the following rules do, please see the checkstyle configuration
|
||||||
|
page at http://checkstyle.sourceforge.net/config.html -->
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
|
||||||
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
<property name="fileNamePattern" value=".*(Example|Test|module-info)(\$.*)?"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<!-- Checks that there are no tab characters in the file.
|
||||||
|
-->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NewlineAtEndOfFile">
|
||||||
|
<property name="lineSeparator" value="lf"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="RegexpSingleline">
|
||||||
|
<!-- Checks that FIXME is not used in comments. TODO is preferred.
|
||||||
|
-->
|
||||||
|
<property name="format" value="((//.*)|(\*.*))FIXME" />
|
||||||
|
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="RegexpSingleline">
|
||||||
|
<!-- Checks that TODOs are named. (Actually, just that they are followed
|
||||||
|
by an open paren.)
|
||||||
|
-->
|
||||||
|
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
|
||||||
|
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="JavadocPackage">
|
||||||
|
<!-- Checks that each Java package has a Javadoc file used for commenting.
|
||||||
|
Only allows a package-info.java, not package.html. -->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- All Java AST specific tests live under TreeWalker module. -->
|
||||||
|
<module name="TreeWalker">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
IMPORT CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="RedundantImport">
|
||||||
|
<!-- Checks for redundant import statements. -->
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="ImportOrder">
|
||||||
|
<!-- Checks for out of order import statements. -->
|
||||||
|
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
<!-- <property name="tokens" value="IMPORT, STATIC_IMPORT"/> -->
|
||||||
|
<property name="separated" value="false"/>
|
||||||
|
<property name="groups" value="*"/>
|
||||||
|
<!-- <property name="option" value="above"/> -->
|
||||||
|
<property name="sortStaticImportsAlphabetically" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="CustomImportOrder">
|
||||||
|
<!-- <property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###SPECIAL_IMPORTS###STANDARD_JAVA_PACKAGE###STATIC"/> -->
|
||||||
|
<!-- <property name="specialImportsRegExp" value="^javax\."/> -->
|
||||||
|
<!-- <property name="standardPackageRegExp" value="^java\."/> -->
|
||||||
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
|
<property name="separateLineBetweenGroups" value="false"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
JAVADOC CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Checks for Javadoc comments. -->
|
||||||
|
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||||
|
<module name="JavadocMethod">
|
||||||
|
<property name="accessModifiers" value="protected"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
<property name="allowMissingParamTags" value="true"/>
|
||||||
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="JavadocType">
|
||||||
|
<property name="scope" value="protected"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="JavadocStyle">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
NAMING CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Item 38 - Adhere to generally accepted naming conventions -->
|
||||||
|
|
||||||
|
<module name="PackageName">
|
||||||
|
<!-- Validates identifiers for package names against the
|
||||||
|
supplied expression. -->
|
||||||
|
<!-- Here the default checkstyle rule restricts package name parts to
|
||||||
|
seven characters, this is not in line with common practice at Google.
|
||||||
|
-->
|
||||||
|
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="TypeNameCheck">
|
||||||
|
<!-- Validates static, final fields against the
|
||||||
|
expression "^[A-Z][a-zA-Z0-9]*$". -->
|
||||||
|
<metadata name="altname" value="TypeName"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="ConstantNameCheck">
|
||||||
|
<!-- Validates non-private, static, final fields against the supplied
|
||||||
|
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
|
||||||
|
<metadata name="altname" value="ConstantName"/>
|
||||||
|
<property name="applyToPublic" value="true"/>
|
||||||
|
<property name="applyToProtected" value="true"/>
|
||||||
|
<property name="applyToPackage" value="true"/>
|
||||||
|
<property name="applyToPrivate" value="false"/>
|
||||||
|
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="StaticVariableNameCheck">
|
||||||
|
<!-- Validates static, non-final fields against the supplied
|
||||||
|
expression "^[a-z][a-zA-Z0-9]*_?$". -->
|
||||||
|
<metadata name="altname" value="StaticVariableName"/>
|
||||||
|
<property name="applyToPublic" value="true"/>
|
||||||
|
<property name="applyToProtected" value="true"/>
|
||||||
|
<property name="applyToPackage" value="true"/>
|
||||||
|
<property name="applyToPrivate" value="true"/>
|
||||||
|
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="MemberNameCheck">
|
||||||
|
<!-- Validates non-static members against the supplied expression. -->
|
||||||
|
<metadata name="altname" value="MemberName"/>
|
||||||
|
<property name="applyToPublic" value="true"/>
|
||||||
|
<property name="applyToProtected" value="true"/>
|
||||||
|
<property name="applyToPackage" value="true"/>
|
||||||
|
<property name="applyToPrivate" value="true"/>
|
||||||
|
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="MethodNameCheck">
|
||||||
|
<!-- Validates identifiers for method names. -->
|
||||||
|
<metadata name="altname" value="MethodName"/>
|
||||||
|
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="ParameterName">
|
||||||
|
<!-- Validates identifiers for method parameters against the
|
||||||
|
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LocalFinalVariableName">
|
||||||
|
<!-- Validates identifiers for local final variables against the
|
||||||
|
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LocalVariableName">
|
||||||
|
<!-- Validates identifiers for local variables against the
|
||||||
|
expression "^[a-z][a-zA-Z0-9]*$". -->
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
LENGTH and CODING CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<!-- Checks for placement of the left curly brace ('{'). -->
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="RightCurly">
|
||||||
|
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
|
||||||
|
the same line. e.g., the following example is fine:
|
||||||
|
<pre>
|
||||||
|
if {
|
||||||
|
...
|
||||||
|
} else
|
||||||
|
</pre>
|
||||||
|
-->
|
||||||
|
<!-- This next example is not fine:
|
||||||
|
<pre>
|
||||||
|
if {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
else
|
||||||
|
</pre>
|
||||||
|
-->
|
||||||
|
<property name="option" value="same"/>
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Checks for braces around if and else blocks -->
|
||||||
|
<module name="NeedBraces">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
<property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="UpperEll">
|
||||||
|
<!-- Checks that long constants are defined with an upper ell.-->
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="FallThrough">
|
||||||
|
<!-- Warn about falling through to the next case statement. Similar to
|
||||||
|
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
|
||||||
|
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
|
||||||
|
some other variants which we don't publicized to promote consistency).
|
||||||
|
-->
|
||||||
|
<property name="reliefPattern"
|
||||||
|
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
MODIFIERS CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="ModifierOrder">
|
||||||
|
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
|
||||||
|
8.4.3. The prescribed order is:
|
||||||
|
public, protected, private, abstract, static, final, transient, volatile,
|
||||||
|
synchronized, native, strictfp
|
||||||
|
-->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
WHITESPACE CHECKS
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="WhitespaceAround">
|
||||||
|
<!-- Checks that various tokens are surrounded by whitespace.
|
||||||
|
This includes most binary operators and keywords followed
|
||||||
|
by regular or curly braces.
|
||||||
|
-->
|
||||||
|
<property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
|
||||||
|
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
|
||||||
|
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
|
||||||
|
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
|
||||||
|
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
|
||||||
|
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
|
||||||
|
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="WhitespaceAfter">
|
||||||
|
<!-- Checks that commas, semicolons and typecasts are followed by
|
||||||
|
whitespace.
|
||||||
|
-->
|
||||||
|
<property name="tokens" value="COMMA, SEMI, TYPECAST"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NoWhitespaceAfter">
|
||||||
|
<!-- Checks that there is no whitespace after various unary operators.
|
||||||
|
Linebreaks are allowed.
|
||||||
|
-->
|
||||||
|
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
|
||||||
|
UNARY_PLUS"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="NoWhitespaceBefore">
|
||||||
|
<!-- Checks that there is no whitespace before various unary operators.
|
||||||
|
Linebreaks are allowed.
|
||||||
|
-->
|
||||||
|
<property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="ParenPad">
|
||||||
|
<!-- Checks that there is no whitespace before close parens or after
|
||||||
|
open parens.
|
||||||
|
-->
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<!-- Checks if a line is too long. -->
|
||||||
|
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The default ignore pattern exempts the following elements:
|
||||||
|
- import statements
|
||||||
|
- long URLs inside comments
|
||||||
|
-->
|
||||||
|
|
||||||
|
<property name="ignorePattern"
|
||||||
|
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
|
||||||
|
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
|
11
gradle/quality/cyclonedx.gradle
Normal file
11
gradle/quality/cyclonedx.gradle
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
cyclonedxBom {
|
||||||
|
includeConfigs = [ 'runtimeClasspath' ]
|
||||||
|
skipConfigs = [ 'compileClasspath', 'testCompileClasspath' ]
|
||||||
|
projectType = "library"
|
||||||
|
schemaVersion = "1.4"
|
||||||
|
destination = file("build/reports")
|
||||||
|
outputName = "bom"
|
||||||
|
outputFormat = "json"
|
||||||
|
includeBomSerialNumber = true
|
||||||
|
componentVersion = "2.0.0"
|
||||||
|
}
|
17
gradle/quality/pmd.gradle
Normal file
17
gradle/quality/pmd.gradle
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
apply plugin: 'pmd'
|
||||||
|
|
||||||
|
tasks.withType(Pmd) {
|
||||||
|
ignoreFailures = true
|
||||||
|
reports {
|
||||||
|
xml.getRequired().set(true)
|
||||||
|
html.getRequired().set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pmd {
|
||||||
|
ignoreFailures = true
|
||||||
|
consoleOutput = false
|
||||||
|
toolVersion = "6.51.0"
|
||||||
|
ruleSetFiles = rootProject.files('gradle/quality/pmd/category/java/bestpractices.xml')
|
||||||
|
}
|
10
gradle/quality/sonarqube.gradle
Normal file
10
gradle/quality/sonarqube.gradle
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.projectName", "${project.group} ${project.name}"
|
||||||
|
property "sonar.sourceEncoding", "UTF-8"
|
||||||
|
property "sonar.tests", "src/test/java"
|
||||||
|
property "sonar.scm.provider", "git"
|
||||||
|
property "sonar.junit.reportsPath", "build/test-results/test/"
|
||||||
|
}
|
||||||
|
}
|
15
gradle/quality/spotbugs.gradle
Normal file
15
gradle/quality/spotbugs.gradle
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
apply plugin: 'com.github.spotbugs'
|
||||||
|
|
||||||
|
spotbugs {
|
||||||
|
//effort = "max"
|
||||||
|
//reportLevel = "low"
|
||||||
|
ignoreFailures = true
|
||||||
|
}
|
||||||
|
|
||||||
|
spotbugsMain {
|
||||||
|
reports {
|
||||||
|
xml.getRequired().set(false)
|
||||||
|
html.getRequired().set(true)
|
||||||
|
}
|
||||||
|
}
|
4
gradle/repositories/maven.gradle
Normal file
4
gradle/repositories/maven.gradle
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
26
gradle/test/junit5.gradle
Normal file
26
gradle/test/junit5.gradle
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
dependencies {
|
||||||
|
testImplementation testLibs.junit.jupiter.api
|
||||||
|
testImplementation testLibs.junit.jupiter.params
|
||||||
|
testImplementation testLibs.hamcrest
|
||||||
|
testRuntimeOnly testLibs.junit.jupiter.engine
|
||||||
|
testRuntimeOnly testLibs.junit.jupiter.platform.launcher
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
failFast = true
|
||||||
|
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
|
||||||
|
testLogging {
|
||||||
|
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'
|
||||||
|
showStandardStreams = true
|
||||||
|
}
|
||||||
|
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.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
249
gradlew
vendored
Executable file
249
gradlew
vendored
Executable file
|
@ -0,0 +1,249 @@
|
||||||
|
#!/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/HEAD/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
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
|
# 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
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
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
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# 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"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 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" "$@"
|
92
gradlew.bat
vendored
Normal file
92
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
@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=.
|
||||||
|
@rem This is normally unused
|
||||||
|
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% equ 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% equ 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!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
3
pgjdbc/build.gradle
Normal file
3
pgjdbc/build.gradle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies {
|
||||||
|
api project(':scram-client')
|
||||||
|
}
|
25
pgjdbc/src/main/java/module-info.java
Normal file
25
pgjdbc/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import java.sql.Driver;
|
||||||
|
|
||||||
|
module org.xbib.jdbc.pgjdbc {
|
||||||
|
requires java.logging;
|
||||||
|
requires java.management;
|
||||||
|
requires java.naming;
|
||||||
|
requires transitive java.sql;
|
||||||
|
requires transitive java.security.jgss;
|
||||||
|
requires org.xbib.scram.client;
|
||||||
|
exports org.postgresql;
|
||||||
|
exports org.postgresql.copy;
|
||||||
|
exports org.postgresql.core;
|
||||||
|
exports org.postgresql.core.v3;
|
||||||
|
exports org.postgresql.fastpath;
|
||||||
|
exports org.postgresql.jdbc;
|
||||||
|
exports org.postgresql.jdbc2;
|
||||||
|
exports org.postgresql.largeobject;
|
||||||
|
exports org.postgresql.replication;
|
||||||
|
exports org.postgresql.replication.fluent;
|
||||||
|
exports org.postgresql.replication.fluent.logical;
|
||||||
|
exports org.postgresql.replication.fluent.physical;
|
||||||
|
exports org.postgresql.util;
|
||||||
|
exports org.postgresql.xml;
|
||||||
|
provides Driver with org.postgresql.Driver;
|
||||||
|
}
|
796
pgjdbc/src/main/java/org/postgresql/Driver.java
Normal file
796
pgjdbc/src/main/java/org/postgresql/Driver.java
Normal file
|
@ -0,0 +1,796 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import org.postgresql.jdbc.PgConnection;
|
||||||
|
import org.postgresql.jdbc.ResourceLock;
|
||||||
|
import org.postgresql.jdbcurlresolver.PgPassParser;
|
||||||
|
import org.postgresql.jdbcurlresolver.PgServiceConfParser;
|
||||||
|
import org.postgresql.util.DriverInfo;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
import org.postgresql.util.PGPropertyUtil;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
import org.postgresql.util.SharedTimer;
|
||||||
|
import org.postgresql.util.URLCoder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.DriverPropertyInfo;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLFeatureNotSupportedException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The Java SQL framework allows for multiple database drivers. Each driver should supply a class
|
||||||
|
* that implements the Driver interface</p>
|
||||||
|
*
|
||||||
|
* <p>The DriverManager will try to load as many drivers as it can find and then for any given
|
||||||
|
* connection request, it will ask each driver in turn to try to connect to the target URL.</p>
|
||||||
|
*
|
||||||
|
* <p>It is strongly recommended that each Driver class should be small and standalone so that the
|
||||||
|
* Driver class can be loaded and queried without bringing in vast quantities of supporting code.</p>
|
||||||
|
*
|
||||||
|
* <p>When a Driver class is loaded, it should create an instance of itself and register it with the
|
||||||
|
* DriverManager. This means that a user can load and register a driver by doing
|
||||||
|
* Class.forName("foo.bah.Driver")</p>
|
||||||
|
*
|
||||||
|
* @see org.postgresql.PGConnection
|
||||||
|
* @see java.sql.Driver
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("try")
|
||||||
|
public class Driver implements java.sql.Driver {
|
||||||
|
|
||||||
|
private static Driver registeredDriver;
|
||||||
|
private static final Logger PARENT_LOGGER = Logger.getLogger("org.postgresql");
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("org.postgresql.Driver");
|
||||||
|
private static final SharedTimer SHARED_TIMER = new SharedTimer();
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
// moved the registerDriver from the constructor to here
|
||||||
|
// because some clients call the driver themselves (I know, as
|
||||||
|
// my early jdbc work did - and that was based on other examples).
|
||||||
|
// Placing it here, means that the driver is registered once only.
|
||||||
|
register();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new ExceptionInInitializerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to retrieve default properties from classloader resource
|
||||||
|
// properties files.
|
||||||
|
private Properties defaultProperties;
|
||||||
|
|
||||||
|
private final ResourceLock lock = new ResourceLock();
|
||||||
|
|
||||||
|
public Driver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties getDefaultProperties() throws IOException {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
if (defaultProperties != null) {
|
||||||
|
return defaultProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we load properties with the maximum possible privileges.
|
||||||
|
try {
|
||||||
|
defaultProperties =
|
||||||
|
doPrivileged(new PrivilegedExceptionAction<Properties>() {
|
||||||
|
@Override
|
||||||
|
public Properties run() throws IOException {
|
||||||
|
return loadDefaultProperties();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (PrivilegedActionException e) {
|
||||||
|
Exception ex = e.getException();
|
||||||
|
if (ex instanceof IOException) {
|
||||||
|
throw (IOException) ex;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (e instanceof IOException) {
|
||||||
|
throw (IOException) e;
|
||||||
|
}
|
||||||
|
if (e instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) e;
|
||||||
|
}
|
||||||
|
if (e instanceof Error) {
|
||||||
|
throw (Error) e;
|
||||||
|
}
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultProperties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T doPrivileged(PrivilegedExceptionAction<T> action) throws Throwable {
|
||||||
|
try {
|
||||||
|
Class<?> accessControllerClass = Class.forName("java.security.AccessController");
|
||||||
|
Method doPrivileged = accessControllerClass.getMethod("doPrivileged",
|
||||||
|
PrivilegedExceptionAction.class);
|
||||||
|
return (T) doPrivileged.invoke(null, action);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return action.run();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties loadDefaultProperties() throws IOException {
|
||||||
|
Properties merged = new Properties();
|
||||||
|
|
||||||
|
try {
|
||||||
|
PGProperty.USER.set(merged, System.getProperty("user.name"));
|
||||||
|
} catch (SecurityException se) {
|
||||||
|
// We're just trying to set a default, so if we can't
|
||||||
|
// it's not a big deal.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are loaded by the bootstrap classloader, getClassLoader()
|
||||||
|
// may return null. In that case, try to fall back to the system
|
||||||
|
// classloader.
|
||||||
|
//
|
||||||
|
// We should not need to catch SecurityException here as we are
|
||||||
|
// accessing either our own classloader, or the system classloader
|
||||||
|
// when our classloader is null. The ClassLoader javadoc claims
|
||||||
|
// neither case can throw SecurityException.
|
||||||
|
ClassLoader cl = getClass().getClassLoader();
|
||||||
|
if (cl == null) {
|
||||||
|
LOGGER.log(Level.FINE, "Can't find our classloader for the Driver; "
|
||||||
|
+ "attempt to use the system class loader");
|
||||||
|
cl = ClassLoader.getSystemClassLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cl == null) {
|
||||||
|
LOGGER.log(Level.WARNING, "Can't find a classloader for the Driver; not loading driver "
|
||||||
|
+ "configuration from org/postgresql/driverconfig.properties");
|
||||||
|
return merged; // Give up on finding defaults.
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.log(Level.FINE, "Loading driver configuration via classloader {0}", cl);
|
||||||
|
|
||||||
|
// When loading the driver config files we don't want settings found
|
||||||
|
// in later files in the classpath to override settings specified in
|
||||||
|
// earlier files. To do this we've got to read the returned
|
||||||
|
// Enumeration into temporary storage.
|
||||||
|
ArrayList<URL> urls = new ArrayList<>();
|
||||||
|
Enumeration<URL> urlEnum = cl.getResources("org/postgresql/driverconfig.properties");
|
||||||
|
while (urlEnum.hasMoreElements()) {
|
||||||
|
urls.add(urlEnum.nextElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = urls.size() - 1; i >= 0; i--) {
|
||||||
|
URL url = urls.get(i);
|
||||||
|
LOGGER.log(Level.FINE, "Loading driver configuration from: {0}", url);
|
||||||
|
InputStream is = url.openStream();
|
||||||
|
merged.load(is);
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Try to make a database connection to the given URL. The driver should return "null" if it
|
||||||
|
* realizes it is the wrong kind of driver to connect to the given URL. This will be common, as
|
||||||
|
* when the JDBC driverManager is asked to connect to a given URL, it passes the URL to each
|
||||||
|
* loaded driver in turn.</p>
|
||||||
|
*
|
||||||
|
* <p>The driver should raise an SQLException if it is the right driver to connect to the given URL,
|
||||||
|
* but has trouble connecting to the database.</p>
|
||||||
|
*
|
||||||
|
* <p>The java.util.Properties argument can be used to pass arbitrary string tag/value pairs as
|
||||||
|
* connection arguments.</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>user - (required) The user to connect as</li>
|
||||||
|
* <li>password - (optional) The password for the user</li>
|
||||||
|
* <li>ssl -(optional) Use SSL when connecting to the server</li>
|
||||||
|
* <li>readOnly - (optional) Set connection to read-only by default</li>
|
||||||
|
* <li>charSet - (optional) The character set to be used for converting to/from
|
||||||
|
* the database to unicode. If multibyte is enabled on the server then the character set of the
|
||||||
|
* database is used as the default, otherwise the jvm character encoding is used as the default.
|
||||||
|
* This value is only used when connecting to a 7.2 or older server.</li>
|
||||||
|
* <li>loglevel - (optional) Enable logging of messages from the driver. The value is an integer
|
||||||
|
* from 0 to 2 where: OFF = 0, INFO =1, DEBUG = 2 The output is sent to
|
||||||
|
* DriverManager.getPrintWriter() if set, otherwise it is sent to System.out.</li>
|
||||||
|
* <li>compatible - (optional) This is used to toggle between different functionality
|
||||||
|
* as it changes across different releases of the jdbc driver code. The values here are versions
|
||||||
|
* of the jdbc client and not server versions. For example in 7.1 get/setBytes worked on
|
||||||
|
* LargeObject values, in 7.2 these methods were changed to work on bytea values. This change in
|
||||||
|
* functionality could be disabled by setting the compatible level to be "7.1", in which case the
|
||||||
|
* driver will revert to the 7.1 functionality.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Normally, at least "user" and "password" properties should be included in the properties. For a
|
||||||
|
* list of supported character encoding , see
|
||||||
|
* http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html Note that you will
|
||||||
|
* probably want to have set up the Postgres database itself to use the same encoding, with the
|
||||||
|
* {@code -E <encoding>} argument to createdb.</p>
|
||||||
|
*
|
||||||
|
* <p>Our protocol takes the forms:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* jdbc:postgresql://host:port/database?param1=val1&...
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param url the URL of the database to connect to
|
||||||
|
* @param info a list of arbitrary tag/value pairs as connection arguments
|
||||||
|
* @return a connection to the URL or null if it isnt us
|
||||||
|
* @exception SQLException if a database access error occurs or the url is
|
||||||
|
* {@code null}
|
||||||
|
* @see java.sql.Driver#connect
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
if (url == null) {
|
||||||
|
throw new SQLException("url is null");
|
||||||
|
}
|
||||||
|
// get defaults
|
||||||
|
Properties defaults;
|
||||||
|
|
||||||
|
if (!url.startsWith("jdbc:postgresql:")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
defaults = getDefaultProperties();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new PSQLException(GT.tr("Error loading default settings from driverconfig.properties"),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// override defaults with provided properties
|
||||||
|
Properties props = new Properties(defaults);
|
||||||
|
if (info != null) {
|
||||||
|
Set<String> e = info.stringPropertyNames();
|
||||||
|
for (String propName : e) {
|
||||||
|
String propValue = info.getProperty(propName);
|
||||||
|
if (propValue == null) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("Properties for the driver contains a non-string value for the key ")
|
||||||
|
+ propName,
|
||||||
|
PSQLState.UNEXPECTED_ERROR);
|
||||||
|
}
|
||||||
|
props.setProperty(propName, propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// parse URL and add more properties
|
||||||
|
if ((props = parseURL(url, props)) == null) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("Unable to parse URL {0}", url),
|
||||||
|
PSQLState.UNEXPECTED_ERROR);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
LOGGER.log(Level.FINE, "Connecting with URL: {0}", url);
|
||||||
|
|
||||||
|
// Enforce login timeout, if specified, by running the connection
|
||||||
|
// attempt in a separate thread. If we hit the timeout without the
|
||||||
|
// connection completing, we abandon the connection attempt in
|
||||||
|
// the calling thread, but the separate thread will keep trying.
|
||||||
|
// Eventually, the separate thread will either fail or complete
|
||||||
|
// the connection; at that point we clean up the connection if
|
||||||
|
// we managed to establish one after all. See ConnectThread for
|
||||||
|
// more details.
|
||||||
|
long timeout = timeout(props);
|
||||||
|
if (timeout <= 0) {
|
||||||
|
return makeConnection(url, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectThread ct = new ConnectThread(url, props);
|
||||||
|
Thread thread = new Thread(ct, "PostgreSQL JDBC driver connection thread");
|
||||||
|
thread.setDaemon(true); // Don't prevent the VM from shutting down
|
||||||
|
thread.start();
|
||||||
|
return ct.getResult(timeout);
|
||||||
|
} catch (PSQLException ex1) {
|
||||||
|
LOGGER.log(Level.FINE, "Connection error: ", ex1);
|
||||||
|
// re-throw the exception, otherwise it will be caught next, and a
|
||||||
|
// org.postgresql.unusual error will be returned instead.
|
||||||
|
throw ex1;
|
||||||
|
} catch (Exception ex2) {
|
||||||
|
if ("java.security.AccessControlException".equals(ex2.getClass().getName())) {
|
||||||
|
// java.security.AccessControlException has been deprecated for removal, so compare the class name
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr(
|
||||||
|
"Your security policy has prevented the connection from being attempted. You probably need to grant the connect java.net.SocketPermission to the database server host and port that you wish to connect to."),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, ex2);
|
||||||
|
}
|
||||||
|
LOGGER.log(Level.FINE, "Unexpected connection error: ", ex2);
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr(
|
||||||
|
"Something unusual has occurred to cause the driver to fail. Please report this exception."),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, ex2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is an empty method left here for graalvm
|
||||||
|
* we removed the ability to setup the logger from properties
|
||||||
|
* due to a security issue
|
||||||
|
* @param props Connection Properties
|
||||||
|
*/
|
||||||
|
private void setupLoggerFromProperties(final Properties props) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a connect in a separate thread; supports getting the results from the original thread
|
||||||
|
* while enforcing a login timeout.
|
||||||
|
*/
|
||||||
|
private static class ConnectThread implements Runnable {
|
||||||
|
private final ResourceLock lock = new ResourceLock();
|
||||||
|
private final Condition lockCondition = lock.newCondition();
|
||||||
|
|
||||||
|
ConnectThread(String url, Properties props) {
|
||||||
|
this.url = url;
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Connection conn;
|
||||||
|
Throwable error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = makeConnection(url, props);
|
||||||
|
error = null;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
conn = null;
|
||||||
|
error = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
if (abandoned) {
|
||||||
|
if (conn != null) {
|
||||||
|
try {
|
||||||
|
conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = conn;
|
||||||
|
resultException = error;
|
||||||
|
lockCondition.signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the connection result from this (assumed running) thread. If the timeout is reached
|
||||||
|
* without a result being available, a SQLException is thrown.
|
||||||
|
*
|
||||||
|
* @param timeout timeout in milliseconds
|
||||||
|
* @return the new connection, if successful
|
||||||
|
* @throws SQLException if a connection error occurs or the timeout is reached
|
||||||
|
*/
|
||||||
|
public Connection getResult(long timeout) throws SQLException {
|
||||||
|
long expiry = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) + timeout;
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
while (true) {
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Throwable resultException = this.resultException;
|
||||||
|
if (resultException != null) {
|
||||||
|
if (resultException instanceof SQLException) {
|
||||||
|
resultException.fillInStackTrace();
|
||||||
|
throw (SQLException) resultException;
|
||||||
|
} else {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr(
|
||||||
|
"Something unusual has occurred to cause the driver to fail. Please report this exception."),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, resultException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long delay = expiry - TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
|
||||||
|
if (delay <= 0) {
|
||||||
|
abandoned = true;
|
||||||
|
throw new PSQLException(GT.tr("Connection attempt timed out."),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
lockCondition.await(delay, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
|
||||||
|
// reset the interrupt flag
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
abandoned = true;
|
||||||
|
|
||||||
|
// throw an unchecked exception which will hopefully not be ignored by the calling code
|
||||||
|
throw new RuntimeException(GT.tr("Interrupted while attempting to connect."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
private final Properties props;
|
||||||
|
private Connection result;
|
||||||
|
private Throwable resultException;
|
||||||
|
private boolean abandoned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a connection from URL and properties. Always does the connection work in the current
|
||||||
|
* thread without enforcing a timeout, regardless of any timeout specified in the properties.
|
||||||
|
*
|
||||||
|
* @param url the original URL
|
||||||
|
* @param props the parsed/defaulted connection properties
|
||||||
|
* @return a new connection
|
||||||
|
* @throws SQLException if the connection could not be made
|
||||||
|
*/
|
||||||
|
private static Connection makeConnection(String url, Properties props) throws SQLException {
|
||||||
|
return new PgConnection(hostSpecs(props), props, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the driver thinks it can open a connection to the given URL. Typically, drivers
|
||||||
|
* will return true if they understand the subprotocol specified in the URL and false if they
|
||||||
|
* don't. Our protocols start with jdbc:postgresql:
|
||||||
|
*
|
||||||
|
* @param url the URL of the driver
|
||||||
|
* @return true if this driver accepts the given URL
|
||||||
|
* @see java.sql.Driver#acceptsURL
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean acceptsURL(String url) {
|
||||||
|
return parseURL(url, null) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The getPropertyInfo method is intended to allow a generic GUI tool to discover what properties
|
||||||
|
* it should prompt a human for in order to get enough information to connect to a database.</p>
|
||||||
|
*
|
||||||
|
* <p>Note that depending on the values the human has supplied so far, additional values may become
|
||||||
|
* necessary, so it may be necessary to iterate through several calls to getPropertyInfo</p>
|
||||||
|
*
|
||||||
|
* @param url the Url of the database to connect to
|
||||||
|
* @param info a proposed list of tag/value pairs that will be sent on connect open.
|
||||||
|
* @return An array of DriverPropertyInfo objects describing possible properties. This array may
|
||||||
|
* be an empty array if no properties are required
|
||||||
|
* @see java.sql.Driver#getPropertyInfo
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
|
||||||
|
Properties copy = new Properties(info);
|
||||||
|
Properties parse = parseURL(url, copy);
|
||||||
|
if (parse != null) {
|
||||||
|
copy = parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
PGProperty[] knownProperties = PGProperty.values();
|
||||||
|
DriverPropertyInfo[] props = new DriverPropertyInfo[knownProperties.length];
|
||||||
|
for (int i = 0; i < props.length; i++) {
|
||||||
|
props[i] = knownProperties[i].toDriverPropertyInfo(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMajorVersion() {
|
||||||
|
return DriverInfo.MAJOR_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinorVersion() {
|
||||||
|
return DriverInfo.MINOR_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server version series of this driver and the specific build number.
|
||||||
|
*
|
||||||
|
* @return JDBC driver version
|
||||||
|
* @deprecated use {@link #getMajorVersion()} and {@link #getMinorVersion()} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static String getVersion() {
|
||||||
|
return DriverInfo.DRIVER_FULL_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Report whether the driver is a genuine JDBC compliant driver. A driver may only report "true"
|
||||||
|
* here if it passes the JDBC compliance tests, otherwise it is required to return false. JDBC
|
||||||
|
* compliance requires full support for the JDBC API and full support for SQL 92 Entry Level.</p>
|
||||||
|
*
|
||||||
|
* <p>For PostgreSQL, this is not yet possible, as we are not SQL92 compliant (yet).</p>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean jdbcCompliant() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new DriverURL, splitting the specified URL into its component parts.
|
||||||
|
*
|
||||||
|
* @param url JDBC URL to parse
|
||||||
|
* @param defaults Default properties
|
||||||
|
* @return Properties with elements added from the url
|
||||||
|
*/
|
||||||
|
public static Properties parseURL(String url, Properties defaults) {
|
||||||
|
// priority 1 - URL values
|
||||||
|
Properties priority1Url = new Properties();
|
||||||
|
// priority 2 - Properties given as argument to DriverManager.getConnection()
|
||||||
|
// argument "defaults" EXCLUDING defaults
|
||||||
|
// priority 3 - Values retrieved by "service"
|
||||||
|
Properties priority3Service = new Properties();
|
||||||
|
// priority 4 - Properties loaded by Driver.loadDefaultProperties() (user, org/postgresql/driverconfig.properties)
|
||||||
|
// argument "defaults" INCLUDING defaults
|
||||||
|
// priority 5 - PGProperty defaults for PGHOST, PGPORT, PGDBNAME
|
||||||
|
|
||||||
|
String urlServer = url;
|
||||||
|
String urlArgs = "";
|
||||||
|
|
||||||
|
int qPos = url.indexOf('?');
|
||||||
|
if (qPos != -1) {
|
||||||
|
urlServer = url.substring(0, qPos);
|
||||||
|
urlArgs = url.substring(qPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!urlServer.startsWith("jdbc:postgresql:")) {
|
||||||
|
LOGGER.log(Level.FINE, "JDBC URL must start with \"jdbc:postgresql:\" but was: {0}", url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
urlServer = urlServer.substring("jdbc:postgresql:".length());
|
||||||
|
|
||||||
|
if ("//".equals(urlServer) || "///".equals(urlServer)) {
|
||||||
|
urlServer = "";
|
||||||
|
} else if (urlServer.startsWith("//")) {
|
||||||
|
urlServer = urlServer.substring(2);
|
||||||
|
long slashCount = urlServer.chars().filter(ch -> ch == '/').count();
|
||||||
|
if (slashCount > 1) {
|
||||||
|
LOGGER.log(Level.WARNING, "JDBC URL contains too many / characters: {0}", url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int slash = urlServer.indexOf('/');
|
||||||
|
if (slash == -1) {
|
||||||
|
LOGGER.log(Level.WARNING, "JDBC URL must contain a / at the end of the host or port: {0}", url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!urlServer.endsWith("/")) {
|
||||||
|
String value = urlDecode(urlServer.substring(slash + 1));
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PGProperty.PG_DBNAME.set(priority1Url, value);
|
||||||
|
}
|
||||||
|
urlServer = urlServer.substring(0, slash);
|
||||||
|
|
||||||
|
String[] addresses = urlServer.split(",");
|
||||||
|
StringBuilder hosts = new StringBuilder();
|
||||||
|
StringBuilder ports = new StringBuilder();
|
||||||
|
for (String address : addresses) {
|
||||||
|
int portIdx = address.lastIndexOf(':');
|
||||||
|
if (portIdx != -1 && address.lastIndexOf(']') < portIdx) {
|
||||||
|
String portStr = address.substring(portIdx + 1);
|
||||||
|
ports.append(portStr);
|
||||||
|
CharSequence hostStr = address.subSequence(0, portIdx);
|
||||||
|
if (hostStr.length() == 0) {
|
||||||
|
hosts.append(PGProperty.PG_HOST.getDefaultValue());
|
||||||
|
} else {
|
||||||
|
hosts.append(hostStr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ports.append(PGProperty.PG_PORT.getDefaultValue());
|
||||||
|
hosts.append(address);
|
||||||
|
}
|
||||||
|
ports.append(',');
|
||||||
|
hosts.append(',');
|
||||||
|
}
|
||||||
|
ports.setLength(ports.length() - 1);
|
||||||
|
hosts.setLength(hosts.length() - 1);
|
||||||
|
PGProperty.PG_HOST.set(priority1Url, hosts.toString());
|
||||||
|
PGProperty.PG_PORT.set(priority1Url, ports.toString());
|
||||||
|
} else if (urlServer.startsWith("/")) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
String value = urlDecode(urlServer);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
priority1Url.setProperty(PGProperty.PG_DBNAME.getName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the args part of the url
|
||||||
|
String[] args = urlArgs.split("&");
|
||||||
|
String serviceName = null;
|
||||||
|
for (String token : args) {
|
||||||
|
if (token.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int pos = token.indexOf('=');
|
||||||
|
if (pos == -1) {
|
||||||
|
priority1Url.setProperty(token, "");
|
||||||
|
} else {
|
||||||
|
String pName = PGPropertyUtil.translatePGServiceToPGProperty(token.substring(0, pos));
|
||||||
|
String pValue = urlDecode(token.substring(pos + 1));
|
||||||
|
if (pValue == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (PGProperty.SERVICE.getName().equals(pName)) {
|
||||||
|
serviceName = pValue;
|
||||||
|
} else {
|
||||||
|
priority1Url.setProperty(pName, pValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load pg_service.conf
|
||||||
|
if (serviceName != null) {
|
||||||
|
LOGGER.log(Level.FINE, "Processing option [?service={0}]", serviceName);
|
||||||
|
Properties result = PgServiceConfParser.getServiceProperties(serviceName);
|
||||||
|
if (result == null) {
|
||||||
|
LOGGER.log(Level.WARNING, "Definition of service [{0}] not found", serviceName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
priority3Service.putAll(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine result based on order of priority
|
||||||
|
Properties result = new Properties();
|
||||||
|
result.putAll(priority1Url);
|
||||||
|
if (defaults != null) {
|
||||||
|
// priority 2 - forEach() returns all entries EXCEPT defaults
|
||||||
|
defaults.forEach(result::putIfAbsent);
|
||||||
|
}
|
||||||
|
priority3Service.forEach(result::putIfAbsent);
|
||||||
|
if (defaults != null) {
|
||||||
|
// priority 4 - stringPropertyNames() returns all entries INCLUDING defaults
|
||||||
|
defaults.stringPropertyNames().forEach(s -> result.putIfAbsent(s, defaults.getProperty(s)));
|
||||||
|
}
|
||||||
|
// priority 5 - PGProperty defaults for PGHOST, PGPORT, PGDBNAME
|
||||||
|
result.putIfAbsent(PGProperty.PG_PORT.getName(), PGProperty.PG_PORT.getDefaultValue());
|
||||||
|
result.putIfAbsent(PGProperty.PG_HOST.getName(), PGProperty.PG_HOST.getDefaultValue());
|
||||||
|
if (PGProperty.USER.getOrDefault(result) != null) {
|
||||||
|
result.putIfAbsent(PGProperty.PG_DBNAME.getName(), PGProperty.USER.getOrDefault(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// consistency check
|
||||||
|
if (!PGPropertyUtil.propertiesConsistencyCheck(result)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to load .pgpass if password is missing
|
||||||
|
if (PGProperty.PASSWORD.getOrDefault(result) == null) {
|
||||||
|
String password = PgPassParser.getPassword(
|
||||||
|
PGProperty.PG_HOST.getOrDefault(result), PGProperty.PG_PORT.getOrDefault(result), PGProperty.PG_DBNAME.getOrDefault(result), PGProperty.USER.getOrDefault(result)
|
||||||
|
);
|
||||||
|
if (password != null && !password.isEmpty()) {
|
||||||
|
PGProperty.PASSWORD.set(result, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode url, on failure log and return null
|
||||||
|
private static String urlDecode(String url) {
|
||||||
|
try {
|
||||||
|
return URLCoder.decode(url);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.log(Level.FINE, "Url [{0}] parsing failed with error [{1}]", new Object[]{url, e.getMessage()});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the address portion of the URL
|
||||||
|
*/
|
||||||
|
private static HostSpec[] hostSpecs(Properties props) {
|
||||||
|
String[] hosts = PGProperty.PG_HOST.getOrDefault(props).split(",");
|
||||||
|
String[] ports = PGProperty.PG_PORT.getOrDefault(props).split(",");
|
||||||
|
String localSocketAddress = PGProperty.LOCAL_SOCKET_ADDRESS.getOrDefault(props);
|
||||||
|
HostSpec[] hostSpecs = new HostSpec[hosts.length];
|
||||||
|
for (int i = 0; i < hostSpecs.length; i++) {
|
||||||
|
hostSpecs[i] = new HostSpec(hosts[i], Integer.parseInt(ports[i]), localSocketAddress);
|
||||||
|
}
|
||||||
|
return hostSpecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the timeout from the URL, in milliseconds
|
||||||
|
*/
|
||||||
|
private static long timeout(Properties props) {
|
||||||
|
String timeout = PGProperty.LOGIN_TIMEOUT.getOrDefault(props);
|
||||||
|
if (timeout != null) {
|
||||||
|
try {
|
||||||
|
return (long) (Float.parseFloat(timeout) * 1000);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Couldn't parse loginTimeout value: {0}", timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (long) DriverManager.getLoginTimeout() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method was added in v6.5, and simply throws an SQLException for an unimplemented method. I
|
||||||
|
* decided to do it this way while implementing the JDBC2 extensions to JDBC, as it should help
|
||||||
|
* keep the overall driver size down. It now requires the call Class and the function name to help
|
||||||
|
* when the driver is used with closed software that don't report the stack trace
|
||||||
|
*
|
||||||
|
* @param callClass the call Class
|
||||||
|
* @param functionName the name of the unimplemented function with the type of its arguments
|
||||||
|
* @return PSQLException with a localized message giving the complete description of the
|
||||||
|
* unimplemented function
|
||||||
|
*/
|
||||||
|
public static SQLFeatureNotSupportedException notImplemented(Class<?> callClass,
|
||||||
|
String functionName) {
|
||||||
|
return new SQLFeatureNotSupportedException(
|
||||||
|
GT.tr("Method {0} is not yet implemented.", callClass.getName() + "." + functionName),
|
||||||
|
PSQLState.NOT_IMPLEMENTED.getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger getParentLogger() {
|
||||||
|
return PARENT_LOGGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SharedTimer getSharedTimer() {
|
||||||
|
return SHARED_TIMER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the driver against {@link DriverManager}. This is done automatically when the class is
|
||||||
|
* loaded. Dropping the driver from DriverManager's list is possible using {@link #deregister()}
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the driver is already registered
|
||||||
|
* @throws SQLException if registering the driver fails
|
||||||
|
*/
|
||||||
|
public static void register() throws SQLException {
|
||||||
|
if (isRegistered()) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Driver is already registered. It can only be registered once.");
|
||||||
|
}
|
||||||
|
Driver registeredDriver = new Driver();
|
||||||
|
DriverManager.registerDriver(registeredDriver);
|
||||||
|
Driver.registeredDriver = registeredDriver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According to JDBC specification, this driver is registered against {@link DriverManager} when
|
||||||
|
* the class is loaded. To avoid leaks, this method allow unregistering the driver so that the
|
||||||
|
* class can be gc'ed if necessary.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the driver is not registered
|
||||||
|
* @throws SQLException if deregistering the driver fails
|
||||||
|
*/
|
||||||
|
public static void deregister() throws SQLException {
|
||||||
|
if (registeredDriver == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Driver is not registered (or it has not been registered using Driver.register() method)");
|
||||||
|
}
|
||||||
|
DriverManager.deregisterDriver(registeredDriver);
|
||||||
|
registeredDriver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the driver is registered against {@link DriverManager}
|
||||||
|
*/
|
||||||
|
public static boolean isRegistered() {
|
||||||
|
return registeredDriver != null;
|
||||||
|
}
|
||||||
|
}
|
381
pgjdbc/src/main/java/org/postgresql/PGConnection.java
Normal file
381
pgjdbc/src/main/java/org/postgresql/PGConnection.java
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import org.postgresql.copy.CopyManager;
|
||||||
|
import org.postgresql.fastpath.Fastpath;
|
||||||
|
import org.postgresql.jdbc.AutoSave;
|
||||||
|
import org.postgresql.jdbc.PreferQueryMode;
|
||||||
|
import org.postgresql.largeobject.LargeObjectManager;
|
||||||
|
import org.postgresql.replication.PGReplicationConnection;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PGobject;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
import org.postgresql.util.PasswordUtil;
|
||||||
|
|
||||||
|
import java.sql.Array;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines the public PostgreSQL extensions to java.sql.Connection. All Connections
|
||||||
|
* returned by the PostgreSQL driver implement PGConnection.
|
||||||
|
*/
|
||||||
|
public interface PGConnection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link Array} wrapping <i>elements</i>. This is similar to
|
||||||
|
* {@link java.sql.Connection#createArrayOf(String, Object[])}, but also
|
||||||
|
* provides support for primitive arrays.
|
||||||
|
*
|
||||||
|
* @param typeName
|
||||||
|
* The SQL name of the type to map the <i>elements</i> to.
|
||||||
|
* Must not be {@code null}.
|
||||||
|
* @param elements
|
||||||
|
* The array of objects to map. A {@code null} value will result in
|
||||||
|
* an {@link Array} representing {@code null}.
|
||||||
|
* @return An {@link Array} wrapping <i>elements</i>.
|
||||||
|
* @throws SQLException
|
||||||
|
* If for some reason the array cannot be created.
|
||||||
|
* @see java.sql.Connection#createArrayOf(String, Object[])
|
||||||
|
*/
|
||||||
|
Array createArrayOf(String typeName, Object elements) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns any notifications that have been received since the last call to this
|
||||||
|
* method. Returns null if there have been no notifications.
|
||||||
|
*
|
||||||
|
* @return notifications that have been received
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 7.3
|
||||||
|
*/
|
||||||
|
PGNotification[] getNotifications() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns any notifications that have been received since the last call to this
|
||||||
|
* method. Returns null if there have been no notifications. A timeout can be specified so the
|
||||||
|
* driver waits for notifications.
|
||||||
|
*
|
||||||
|
* @param timeoutMillis when 0, blocks forever. when > 0, blocks up to the specified number of millis
|
||||||
|
* or until at least one notification has been received. If more than one notification is
|
||||||
|
* about to be received, these will be returned in one batch.
|
||||||
|
* @return notifications that have been received
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 43
|
||||||
|
*/
|
||||||
|
PGNotification[] getNotifications(int timeoutMillis) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the COPY API for the current connection.
|
||||||
|
*
|
||||||
|
* @return COPY API for the current connection
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 8.4
|
||||||
|
*/
|
||||||
|
CopyManager getCopyAPI() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the LargeObject API for the current connection.
|
||||||
|
*
|
||||||
|
* @return LargeObject API for the current connection
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 7.3
|
||||||
|
*/
|
||||||
|
LargeObjectManager getLargeObjectAPI() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns the Fastpath API for the current connection.
|
||||||
|
*
|
||||||
|
* @return Fastpath API for the current connection
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 7.3
|
||||||
|
* @deprecated This API is somewhat obsolete, as one may achieve similar performance
|
||||||
|
* and greater functionality by setting up a prepared statement to define
|
||||||
|
* the function call. Then, executing the statement with binary transmission of parameters
|
||||||
|
* and results substitutes for a fast-path function call.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Fastpath getFastpathAPI() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows client code to add a handler for one of org.postgresql's more unique data types. It
|
||||||
|
* is approximately equivalent to <code>addDataType(type, Class.forName(name))</code>.
|
||||||
|
*
|
||||||
|
* @param type JDBC type name
|
||||||
|
* @param className class name
|
||||||
|
* @throws RuntimeException if the type cannot be registered (class not found, etc).
|
||||||
|
* @deprecated As of 8.0, replaced by {@link #addDataType(String, Class)}. This deprecated method
|
||||||
|
* does not work correctly for registering classes that cannot be directly loaded by
|
||||||
|
* the JDBC driver's classloader.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void addDataType(String type, String className);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This allows client code to add a handler for one of org.postgresql's more unique data types.</p>
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> This is not part of JDBC, but an extension.</p>
|
||||||
|
*
|
||||||
|
* <p>The best way to use this is as follows:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* ...
|
||||||
|
* ((org.postgresql.PGConnection)myconn).addDataType("mytype", my.class.name.class);
|
||||||
|
* ...
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>where myconn is an open Connection to org.postgresql.</p>
|
||||||
|
*
|
||||||
|
* <p>The handling class must extend org.postgresql.util.PGobject</p>
|
||||||
|
*
|
||||||
|
* @param type the PostgreSQL type to register
|
||||||
|
* @param klass the class implementing the Java representation of the type; this class must
|
||||||
|
* implement {@link org.postgresql.util.PGobject}).
|
||||||
|
* @throws SQLException if <code>klass</code> does not implement
|
||||||
|
* {@link org.postgresql.util.PGobject}).
|
||||||
|
* @see org.postgresql.util.PGobject
|
||||||
|
* @since 8.0
|
||||||
|
*/
|
||||||
|
void addDataType(String type, Class<? extends PGobject> klass) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default statement reuse threshold before enabling server-side prepare. See
|
||||||
|
* {@link org.postgresql.PGStatement#setPrepareThreshold(int)} for details.
|
||||||
|
*
|
||||||
|
* @param threshold the new threshold
|
||||||
|
* @since build 302
|
||||||
|
*/
|
||||||
|
void setPrepareThreshold(int threshold);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default server-side prepare reuse threshold for statements created from this
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* @return the current threshold
|
||||||
|
* @since build 302
|
||||||
|
*/
|
||||||
|
int getPrepareThreshold();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default fetch size for statements created from this connection.
|
||||||
|
*
|
||||||
|
* @param fetchSize new default fetch size
|
||||||
|
* @throws SQLException if specified negative <code>fetchSize</code> parameter
|
||||||
|
* @see Statement#setFetchSize(int)
|
||||||
|
*/
|
||||||
|
void setDefaultFetchSize(int fetchSize) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default fetch size for statements created from this connection.
|
||||||
|
*
|
||||||
|
* @return current state for default fetch size
|
||||||
|
* @see PGProperty#DEFAULT_ROW_FETCH_SIZE
|
||||||
|
* @see Statement#getFetchSize()
|
||||||
|
*/
|
||||||
|
int getDefaultFetchSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the process ID (PID) of the backend server process handling this connection.
|
||||||
|
*
|
||||||
|
* @return PID of backend server process.
|
||||||
|
*/
|
||||||
|
int getBackendPID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a query cancellation for this connection.
|
||||||
|
* @throws SQLException if there are problems cancelling the query
|
||||||
|
*/
|
||||||
|
void cancelQuery() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the given string suitably quoted to be used as an identifier in an SQL statement string.
|
||||||
|
* Quotes are added only if necessary (i.e., if the string contains non-identifier characters or
|
||||||
|
* would be case-folded). Embedded quotes are properly doubled.
|
||||||
|
*
|
||||||
|
* @param identifier input identifier
|
||||||
|
* @return the escaped identifier
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
String escapeIdentifier(String identifier) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the given string suitably quoted to be used as a string literal in an SQL statement
|
||||||
|
* string. Embedded single-quotes and backslashes are properly doubled. Note that quote_literal
|
||||||
|
* returns null on null input.
|
||||||
|
*
|
||||||
|
* @param literal input literal
|
||||||
|
* @return the quoted literal
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
String escapeLiteral(String literal) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns the query mode for this connection.</p>
|
||||||
|
*
|
||||||
|
* <p>When running in simple query mode, certain features are not available: callable statements,
|
||||||
|
* partial result set fetch, bytea type, etc.</p>
|
||||||
|
* <p>The list of supported features is subject to change.</p>
|
||||||
|
*
|
||||||
|
* @return the preferred query mode
|
||||||
|
* @see PreferQueryMode
|
||||||
|
*/
|
||||||
|
PreferQueryMode getPreferQueryMode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection configuration regarding automatic per-query savepoints.
|
||||||
|
*
|
||||||
|
* @return connection configuration regarding automatic per-query savepoints
|
||||||
|
* @see PGProperty#AUTOSAVE
|
||||||
|
*/
|
||||||
|
AutoSave getAutosave();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures if connection should use automatic savepoints.
|
||||||
|
* @param autoSave connection configuration regarding automatic per-query savepoints
|
||||||
|
* @see PGProperty#AUTOSAVE
|
||||||
|
*/
|
||||||
|
void setAutosave(AutoSave autoSave);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return replication API for the current connection
|
||||||
|
*/
|
||||||
|
PGReplicationConnection getReplicationAPI();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change a user's password to the specified new password.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the specific encryption type is not specified, this method defaults to querying the database server for the server's default password_encryption.
|
||||||
|
* This method does not send the new password in plain text to the server.
|
||||||
|
* Instead, it encrypts the password locally and sends the encoded hash so that the plain text password is never sent on the wire.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Acceptable values for encryptionType are null, "md5", or "scram-sha-256".
|
||||||
|
* Users should avoid "md5" unless they are explicitly targeting an older server that does not support the more secure SCRAM.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param user The username of the database user
|
||||||
|
* @param newPassword The new password for the database user. The implementation will zero
|
||||||
|
* out the array after use
|
||||||
|
* @param encryptionType The type of password encryption to use or null if the database server default should be used.
|
||||||
|
* @throws SQLException If the password could not be altered
|
||||||
|
*/
|
||||||
|
default void alterUserPassword(String user, char[] newPassword, String encryptionType) throws SQLException {
|
||||||
|
try (Statement stmt = ((Connection) this).createStatement()) {
|
||||||
|
if (encryptionType == null) {
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SHOW password_encryption")) {
|
||||||
|
if (!rs.next()) {
|
||||||
|
throw new PSQLException(GT.tr("Expected a row when reading password_encryption but none was found"),
|
||||||
|
PSQLState.NO_DATA);
|
||||||
|
}
|
||||||
|
encryptionType = rs.getString(1);
|
||||||
|
if (encryptionType == null) {
|
||||||
|
throw new PSQLException(GT.tr("SHOW password_encryption returned null value"),
|
||||||
|
PSQLState.NO_DATA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String sql = PasswordUtil.genAlterUserPasswordSQL(user, newPassword, encryptionType);
|
||||||
|
stmt.execute(sql);
|
||||||
|
} finally {
|
||||||
|
Arrays.fill(newPassword, (char) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns the current values of all parameters reported by the server.</p>
|
||||||
|
*
|
||||||
|
* <p>PostgreSQL reports values for a subset of parameters (GUCs) to the client
|
||||||
|
* at connect-time, then sends update messages whenever the values change
|
||||||
|
* during a session. PgJDBC records the latest values and exposes it to client
|
||||||
|
* applications via <code>getParameterStatuses()</code>.</p>
|
||||||
|
*
|
||||||
|
* <p>PgJDBC exposes individual accessors for some of these parameters as
|
||||||
|
* listed below. They are more backwards-compatible and should be preferred
|
||||||
|
* where possible.</p>
|
||||||
|
*
|
||||||
|
* <p>Not all parameters are reported, only those marked
|
||||||
|
* <code>GUC_REPORT</code> in the source code. The <code>pg_settings</code>
|
||||||
|
* view does not expose information about which parameters are reportable.
|
||||||
|
* PgJDBC's map will only contain the parameters the server reports values
|
||||||
|
* for, so you cannot use this method as a substitute for running a
|
||||||
|
* <code>SHOW paramname;</code> or <code>SELECT
|
||||||
|
* current_setting('paramname');</code> query for arbitrary parameters.</p>
|
||||||
|
*
|
||||||
|
* <p>Parameter names are <i>case-insensitive</i> and <i>case-preserving</i>
|
||||||
|
* in this map, like in PostgreSQL itself. So <code>DateStyle</code> and
|
||||||
|
* <code>datestyle</code> are the same key.</p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* As of PostgreSQL 11 the reportable parameter list, and related PgJDBC
|
||||||
|
* interfaces or assessors, are:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>
|
||||||
|
* <code>application_name</code> -
|
||||||
|
* {@link java.sql.Connection#getClientInfo()},
|
||||||
|
* {@link java.sql.Connection#setClientInfo(java.util.Properties)}
|
||||||
|
* and <code>ApplicationName</code> connection property.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* <code>client_encoding</code> - PgJDBC always sets this to <code>UTF8</code>.
|
||||||
|
* See <code>allowEncodingChanges</code> connection property.
|
||||||
|
* </li>
|
||||||
|
* <li><code>DateStyle</code> - PgJDBC requires this to always be set to <code>ISO</code></li>
|
||||||
|
* <li><code>standard_conforming_strings</code> - indirectly via {@link #escapeLiteral(String)}</li>
|
||||||
|
* <li>
|
||||||
|
* <code>TimeZone</code> - set from JDK timezone see {@link java.util.TimeZone#getDefault()}
|
||||||
|
* and {@link java.util.TimeZone#setDefault(TimeZone)}
|
||||||
|
* </li>
|
||||||
|
* <li><code>integer_datetimes</code></li>
|
||||||
|
* <li><code>IntervalStyle</code></li>
|
||||||
|
* <li><code>server_encoding</code></li>
|
||||||
|
* <li><code>server_version</code></li>
|
||||||
|
* <li><code>is_superuser</code> </li>
|
||||||
|
* <li><code>session_authorization</code></li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Note that some PgJDBC operations will change server parameters
|
||||||
|
* automatically.</p>
|
||||||
|
*
|
||||||
|
* @return unmodifiable map of case-insensitive parameter names to parameter values
|
||||||
|
* @since 42.2.6
|
||||||
|
*/
|
||||||
|
Map<String, String> getParameterStatuses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for getParameterStatuses().get(...) .
|
||||||
|
*
|
||||||
|
* @param parameterName case-insensitive parameter name
|
||||||
|
* @return parameter value if defined, or null if no parameter known
|
||||||
|
* @see #getParameterStatuses
|
||||||
|
* @since 42.2.6
|
||||||
|
*/
|
||||||
|
String getParameterStatus(String parameterName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on/off adaptive fetch for connection. Existing statements and resultSets won't be affected
|
||||||
|
* by change here.
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch desired state of adaptive fetch.
|
||||||
|
*/
|
||||||
|
void setAdaptiveFetch(boolean adaptiveFetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state of adaptive fetch for connection.
|
||||||
|
*
|
||||||
|
* @return state of adaptive fetch (turned on or off)
|
||||||
|
*/
|
||||||
|
boolean getAdaptiveFetch();
|
||||||
|
}
|
108
pgjdbc/src/main/java/org/postgresql/PGEnvironment.java
Normal file
108
pgjdbc/src/main/java/org/postgresql/PGEnvironment.java
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some environment variables are intended to have same meaning as libpq describes here:
|
||||||
|
* https://www.postgresql.org/docs/current/libpq-envars.html
|
||||||
|
*/
|
||||||
|
public enum PGEnvironment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specified location of password file.
|
||||||
|
*/
|
||||||
|
ORG_POSTGRESQL_PGPASSFILE(
|
||||||
|
"org.postgresql.pgpassfile",
|
||||||
|
null,
|
||||||
|
"Specified location of password file."),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specified location of password file.
|
||||||
|
*/
|
||||||
|
PGPASSFILE(
|
||||||
|
"PGPASSFILE",
|
||||||
|
"pgpass",
|
||||||
|
"Specified location of password file."),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection service resource (file, url) allows connection parameters to be associated
|
||||||
|
* with a single service name.
|
||||||
|
*/
|
||||||
|
ORG_POSTGRESQL_PGSERVICEFILE(
|
||||||
|
"org.postgresql.pgservicefile",
|
||||||
|
null,
|
||||||
|
"Specifies the service resource to resolve connection properties."),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection service resource (file, url) allows connection parameters to be associated
|
||||||
|
* with a single service name.
|
||||||
|
*/
|
||||||
|
PGSERVICEFILE(
|
||||||
|
"PGSERVICEFILE",
|
||||||
|
"pg_service.conf",
|
||||||
|
"Specifies the service resource to resolve connection properties."),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the directory containing the PGSERVICEFILE file and possibly other system-wide
|
||||||
|
* configuration files.
|
||||||
|
*/
|
||||||
|
PGSYSCONFDIR(
|
||||||
|
"PGSYSCONFDIR",
|
||||||
|
null,
|
||||||
|
"Specifies the directory containing the PGSERVICEFILE file"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String defaultValue;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
PGEnvironment(String name, String defaultValue, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, PGEnvironment> PROPS_BY_NAME = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (PGEnvironment prop : PGEnvironment.values()) {
|
||||||
|
if (PROPS_BY_NAME.put(prop.getName(), prop) != null) {
|
||||||
|
throw new IllegalStateException("Duplicate PGProperty name: " + prop.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the parameter.
|
||||||
|
*
|
||||||
|
* @return the name of the parameter
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default value for this parameter.
|
||||||
|
*
|
||||||
|
* @return the default value for this parameter or null
|
||||||
|
*/
|
||||||
|
public String getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the description for this parameter.
|
||||||
|
*
|
||||||
|
* @return the description for this parameter
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
pgjdbc/src/main/java/org/postgresql/PGNotification.java
Normal file
37
pgjdbc/src/main/java/org/postgresql/PGNotification.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines the public PostgreSQL extension for Notifications.
|
||||||
|
*/
|
||||||
|
public interface PGNotification {
|
||||||
|
/**
|
||||||
|
* Returns name of this notification.
|
||||||
|
*
|
||||||
|
* @return name of this notification
|
||||||
|
* @since 7.3
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the process id of the backend process making this notification.
|
||||||
|
*
|
||||||
|
* @return process id of the backend process making this notification
|
||||||
|
* @since 7.3
|
||||||
|
*/
|
||||||
|
int getPID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns additional information from the notifying process. This feature has only been
|
||||||
|
* implemented in server versions 9.0 and later, so previous versions will always return an empty
|
||||||
|
* String.
|
||||||
|
*
|
||||||
|
* @return additional information from the notifying process
|
||||||
|
* @since 8.0
|
||||||
|
*/
|
||||||
|
String getParameter();
|
||||||
|
}
|
1031
pgjdbc/src/main/java/org/postgresql/PGProperty.java
Normal file
1031
pgjdbc/src/main/java/org/postgresql/PGProperty.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ref cursor based result set.
|
||||||
|
*
|
||||||
|
* @deprecated As of 8.0, this interface is only present for backwards- compatibility purposes. New
|
||||||
|
* code should call getString() on the ResultSet that contains the refcursor to obtain
|
||||||
|
* the underlying cursor name.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public interface PGRefCursorResultSet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the cursor.
|
||||||
|
* @deprecated As of 8.0, replaced with calling getString() on the ResultSet that this ResultSet
|
||||||
|
* was obtained from.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
String getRefCursor();
|
||||||
|
}
|
55
pgjdbc/src/main/java/org/postgresql/PGResultSetMetaData.java
Normal file
55
pgjdbc/src/main/java/org/postgresql/PGResultSetMetaData.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import org.postgresql.core.Field;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public interface PGResultSetMetaData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the underlying column name of a query result, or "" if it is unable to be determined.
|
||||||
|
*
|
||||||
|
* @param column column position (1-based)
|
||||||
|
* @return underlying column name of a query result
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 8.0
|
||||||
|
*/
|
||||||
|
String getBaseColumnName(int column) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the underlying table name of query result, or "" if it is unable to be determined.
|
||||||
|
*
|
||||||
|
* @param column column position (1-based)
|
||||||
|
* @return underlying table name of query result
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 8.0
|
||||||
|
*/
|
||||||
|
String getBaseTableName(int column) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the underlying schema name of query result, or "" if it is unable to be determined.
|
||||||
|
*
|
||||||
|
* @param column column position (1-based)
|
||||||
|
* @return underlying schema name of query result
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @since 8.0
|
||||||
|
*/
|
||||||
|
String getBaseSchemaName(int column) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is a column Text or Binary?
|
||||||
|
*
|
||||||
|
* @param column column position (1-based)
|
||||||
|
* @return 0 if column data format is TEXT, or 1 if BINARY
|
||||||
|
* @throws SQLException if something wrong happens
|
||||||
|
* @see Field#BINARY_FORMAT
|
||||||
|
* @see Field#TEXT_FORMAT
|
||||||
|
* @since 9.4
|
||||||
|
*/
|
||||||
|
int getFormat(int column) throws SQLException;
|
||||||
|
}
|
97
pgjdbc/src/main/java/org/postgresql/PGStatement.java
Normal file
97
pgjdbc/src/main/java/org/postgresql/PGStatement.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines the public PostgreSQL extensions to java.sql.Statement. All Statements
|
||||||
|
* constructed by the PostgreSQL driver implement PGStatement.
|
||||||
|
*/
|
||||||
|
public interface PGStatement {
|
||||||
|
// We can't use Long.MAX_VALUE or Long.MIN_VALUE for java.sql.date
|
||||||
|
// because this would break the 'normalization contract' of the
|
||||||
|
// java.sql.Date API.
|
||||||
|
// The follow values are the nearest MAX/MIN values with hour,
|
||||||
|
// minute, second, millisecond set to 0 - this is used for
|
||||||
|
// -infinity / infinity representation in Java
|
||||||
|
long DATE_POSITIVE_INFINITY = 9223372036825200000L;
|
||||||
|
long DATE_NEGATIVE_INFINITY = -9223372036832400000L;
|
||||||
|
long DATE_POSITIVE_SMALLER_INFINITY = 185543533774800000L;
|
||||||
|
long DATE_NEGATIVE_SMALLER_INFINITY = -185543533774800000L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Last inserted/updated oid.
|
||||||
|
*
|
||||||
|
* @return OID of last insert
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
* @since 7.3
|
||||||
|
*/
|
||||||
|
long getLastOID() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on the use of prepared statements in the server (server side prepared statements are
|
||||||
|
* unrelated to jdbc PreparedStatements) As of build 302, this method is equivalent to
|
||||||
|
* <code>setPrepareThreshold(1)</code>.
|
||||||
|
*
|
||||||
|
* @param flag use server prepare
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
* @since 7.3
|
||||||
|
* @deprecated As of build 302, replaced by {@link #setPrepareThreshold(int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void setUseServerPrepare(boolean flag) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this statement will be executed as a server-prepared statement. A return value of
|
||||||
|
* <code>true</code> indicates that the next execution of the statement will be done as a
|
||||||
|
* server-prepared statement, assuming the underlying protocol supports it.
|
||||||
|
*
|
||||||
|
* @return true if the next reuse of this statement will use a server-prepared statement
|
||||||
|
*/
|
||||||
|
boolean isUseServerPrepare();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the reuse threshold for using server-prepared statements.</p>
|
||||||
|
*
|
||||||
|
* <p>If <code>threshold</code> is a non-zero value N, the Nth and subsequent reuses of a
|
||||||
|
* PreparedStatement will use server-side prepare.</p>
|
||||||
|
*
|
||||||
|
* <p>If <code>threshold</code> is zero, server-side prepare will not be used.</p>
|
||||||
|
*
|
||||||
|
* <p>The reuse threshold is only used by PreparedStatement and CallableStatement objects; it is
|
||||||
|
* ignored for plain Statements.</p>
|
||||||
|
*
|
||||||
|
* @param threshold the new threshold for this statement
|
||||||
|
* @throws SQLException if an exception occurs while changing the threshold
|
||||||
|
* @since build 302
|
||||||
|
*/
|
||||||
|
void setPrepareThreshold(int threshold) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the server-side prepare reuse threshold in use for this statement.
|
||||||
|
*
|
||||||
|
* @return the current threshold
|
||||||
|
* @see #setPrepareThreshold(int)
|
||||||
|
* @since build 302
|
||||||
|
*/
|
||||||
|
int getPrepareThreshold();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on/off adaptive fetch for statement. Existing resultSets won't be affected by change
|
||||||
|
* here.
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch desired state of adaptive fetch.
|
||||||
|
*/
|
||||||
|
void setAdaptiveFetch(boolean adaptiveFetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state of adaptive fetch for statement.
|
||||||
|
*
|
||||||
|
* @return state of adaptive fetch (turned on or off)
|
||||||
|
*/
|
||||||
|
boolean getAdaptiveFetch();
|
||||||
|
}
|
16
pgjdbc/src/main/java/org/postgresql/copy/CopyDual.java
Normal file
16
pgjdbc/src/main/java/org/postgresql/copy/CopyDual.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional via copy stream protocol. Via bidirectional copy protocol work PostgreSQL
|
||||||
|
* replication.
|
||||||
|
*
|
||||||
|
* @see CopyIn
|
||||||
|
* @see CopyOut
|
||||||
|
*/
|
||||||
|
public interface CopyDual extends CopyIn, CopyOut {
|
||||||
|
}
|
52
pgjdbc/src/main/java/org/postgresql/copy/CopyIn.java
Normal file
52
pgjdbc/src/main/java/org/postgresql/copy/CopyIn.java
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy bulk data from client into a PostgreSQL table very fast.
|
||||||
|
*/
|
||||||
|
public interface CopyIn extends CopyOperation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes specified part of given byte array to an open and writable copy operation.
|
||||||
|
*
|
||||||
|
* @param buf array of bytes to write
|
||||||
|
* @param off offset of first byte to write (normally zero)
|
||||||
|
* @param siz number of bytes to write (normally buf.length)
|
||||||
|
* @throws SQLException if the operation fails
|
||||||
|
*/
|
||||||
|
void writeToCopy(byte[] buf, int off, int siz) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a ByteStreamWriter to an open and writable copy operation.
|
||||||
|
*
|
||||||
|
* @param from the source of bytes, e.g. a ByteBufferByteStreamWriter
|
||||||
|
* @throws SQLException if the operation fails
|
||||||
|
*/
|
||||||
|
void writeToCopy(ByteStreamWriter from) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force any buffered output to be sent over the network to the backend. In general this is a
|
||||||
|
* useless operation as it will get pushed over in due time or when endCopy is called. Some
|
||||||
|
* specific modified server versions (Truviso) want this data sooner. If you are unsure if you
|
||||||
|
* need to use this method, don't.
|
||||||
|
*
|
||||||
|
* @throws SQLException if the operation fails.
|
||||||
|
*/
|
||||||
|
void flushCopy() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes copy operation successfully.
|
||||||
|
*
|
||||||
|
* @return number of updated rows for server 8.2 or newer (see getHandledRowCount())
|
||||||
|
* @throws SQLException if the operation fails.
|
||||||
|
*/
|
||||||
|
long endCopy() throws SQLException;
|
||||||
|
}
|
256
pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java
Normal file
256
pgjdbc/src/main/java/org/postgresql/copy/CopyManager.java
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import org.postgresql.core.BaseConnection;
|
||||||
|
import org.postgresql.core.Encoding;
|
||||||
|
import org.postgresql.core.QueryExecutor;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for PostgreSQL COPY bulk data transfer.
|
||||||
|
*/
|
||||||
|
public class CopyManager {
|
||||||
|
// I don't know what the best buffer size is, so we let people specify it if
|
||||||
|
// they want, and if they don't know, we don't make them guess, so that if we
|
||||||
|
// do figure it out we can just set it here and they reap the rewards.
|
||||||
|
// Note that this is currently being used for both a number of bytes and a number
|
||||||
|
// of characters.
|
||||||
|
static final int DEFAULT_BUFFER_SIZE = 65536;
|
||||||
|
|
||||||
|
private final Encoding encoding;
|
||||||
|
private final QueryExecutor queryExecutor;
|
||||||
|
private final BaseConnection connection;
|
||||||
|
|
||||||
|
public CopyManager(BaseConnection connection) throws SQLException {
|
||||||
|
this.encoding = connection.getEncoding();
|
||||||
|
this.queryExecutor = connection.getQueryExecutor();
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyIn copyIn(String sql) throws SQLException {
|
||||||
|
CopyOperation op = queryExecutor.startCopy(sql, connection.getAutoCommit());
|
||||||
|
if (op == null || op instanceof CopyIn) {
|
||||||
|
return (CopyIn) op;
|
||||||
|
} else {
|
||||||
|
op.cancelCopy();
|
||||||
|
throw new PSQLException(GT.tr("Requested CopyIn but got {0}", op.getClass().getName()),
|
||||||
|
PSQLState.WRONG_OBJECT_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyOut copyOut(String sql) throws SQLException {
|
||||||
|
CopyOperation op = queryExecutor.startCopy(sql, connection.getAutoCommit());
|
||||||
|
if (op == null || op instanceof CopyOut) {
|
||||||
|
return (CopyOut) op;
|
||||||
|
} else {
|
||||||
|
op.cancelCopy();
|
||||||
|
throw new PSQLException(GT.tr("Requested CopyOut but got {0}", op.getClass().getName()),
|
||||||
|
PSQLState.WRONG_OBJECT_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyDual copyDual(String sql) throws SQLException {
|
||||||
|
CopyOperation op = queryExecutor.startCopy(sql, connection.getAutoCommit());
|
||||||
|
if (op == null || op instanceof CopyDual) {
|
||||||
|
return (CopyDual) op;
|
||||||
|
} else {
|
||||||
|
op.cancelCopy();
|
||||||
|
throw new PSQLException(GT.tr("Requested CopyDual but got {0}", op.getClass().getName()),
|
||||||
|
PSQLState.WRONG_OBJECT_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass results of a COPY TO STDOUT query from database into a Writer.
|
||||||
|
*
|
||||||
|
* @param sql COPY TO STDOUT statement
|
||||||
|
* @param to the Writer to write the results to (row by row).
|
||||||
|
* The Writer is not closed at the end of the Copy Out operation.
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage errors
|
||||||
|
* @throws IOException upon writer or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyOut(final String sql, Writer to) throws SQLException, IOException {
|
||||||
|
byte[] buf;
|
||||||
|
CopyOut cp = copyOut(sql);
|
||||||
|
try {
|
||||||
|
while ((buf = cp.readFromCopy()) != null) {
|
||||||
|
to.write(encoding.decode(buf));
|
||||||
|
}
|
||||||
|
return cp.getHandledRowCount();
|
||||||
|
} catch (IOException ioEX) {
|
||||||
|
// if not handled this way the close call will hang, at least in 8.2
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
try { // read until exhausted or operation cancelled SQLException
|
||||||
|
while ((buf = cp.readFromCopy()) != null) {
|
||||||
|
}
|
||||||
|
} catch (SQLException sqlEx) {
|
||||||
|
} // typically after several kB
|
||||||
|
throw ioEX;
|
||||||
|
} finally { // see to it that we do not leave the connection locked
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass results of a COPY TO STDOUT query from database into an OutputStream.
|
||||||
|
*
|
||||||
|
* @param sql COPY TO STDOUT statement
|
||||||
|
* @param to the stream to write the results to (row by row)
|
||||||
|
* The stream is not closed at the end of the operation. This is intentional so the
|
||||||
|
* caller can continue to write to the output stream
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage errors
|
||||||
|
* @throws IOException upon output stream or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyOut(final String sql, OutputStream to) throws SQLException, IOException {
|
||||||
|
byte[] buf;
|
||||||
|
CopyOut cp = copyOut(sql);
|
||||||
|
try {
|
||||||
|
while ((buf = cp.readFromCopy()) != null) {
|
||||||
|
to.write(buf);
|
||||||
|
}
|
||||||
|
return cp.getHandledRowCount();
|
||||||
|
} catch (IOException ioEX) {
|
||||||
|
// if not handled this way the close call will hang, at least in 8.2
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
try { // read until exhausted or operation cancelled SQLException
|
||||||
|
while ((buf = cp.readFromCopy()) != null) {
|
||||||
|
}
|
||||||
|
} catch (SQLException sqlEx) {
|
||||||
|
} // typically after several kB
|
||||||
|
throw ioEX;
|
||||||
|
} finally { // see to it that we do not leave the connection locked
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use COPY FROM STDIN for very fast copying from a Reader into a database table.
|
||||||
|
*
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param from a CSV file or such
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage issues
|
||||||
|
* @throws IOException upon reader or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyIn(final String sql, Reader from) throws SQLException, IOException {
|
||||||
|
return copyIn(sql, from, DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use COPY FROM STDIN for very fast copying from a Reader into a database table.
|
||||||
|
*
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param from a CSV file or such
|
||||||
|
* @param bufferSize number of characters to buffer and push over network to server at once
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage issues
|
||||||
|
* @throws IOException upon reader or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyIn(final String sql, Reader from, int bufferSize)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
char[] cbuf = new char[bufferSize];
|
||||||
|
int len;
|
||||||
|
CopyIn cp = copyIn(sql);
|
||||||
|
try {
|
||||||
|
while ((len = from.read(cbuf)) >= 0) {
|
||||||
|
if (len > 0) {
|
||||||
|
byte[] buf = encoding.encode(new String(cbuf, 0, len));
|
||||||
|
cp.writeToCopy(buf, 0, buf.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cp.endCopy();
|
||||||
|
} finally { // see to it that we do not leave the connection locked
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use COPY FROM STDIN for very fast copying from an InputStream into a database table.
|
||||||
|
*
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param from a CSV file or such
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage issues
|
||||||
|
* @throws IOException upon input stream or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyIn(final String sql, InputStream from) throws SQLException, IOException {
|
||||||
|
return copyIn(sql, from, DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use COPY FROM STDIN for very fast copying from an InputStream into a database table.
|
||||||
|
*
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param from a CSV file or such
|
||||||
|
* @param bufferSize number of bytes to buffer and push over network to server at once
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage issues
|
||||||
|
* @throws IOException upon input stream or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyIn(final String sql, InputStream from, int bufferSize)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
byte[] buf = new byte[bufferSize];
|
||||||
|
int len;
|
||||||
|
CopyIn cp = copyIn(sql);
|
||||||
|
try {
|
||||||
|
while ((len = from.read(buf)) >= 0) {
|
||||||
|
if (len > 0) {
|
||||||
|
cp.writeToCopy(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cp.endCopy();
|
||||||
|
} finally { // see to it that we do not leave the connection locked
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use COPY FROM STDIN for very fast copying from an ByteStreamWriter into a database table.
|
||||||
|
*
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param from the source of bytes, e.g. a ByteBufferByteStreamWriter
|
||||||
|
* @return number of rows updated for server 8.2 or newer; -1 for older
|
||||||
|
* @throws SQLException on database usage issues
|
||||||
|
* @throws IOException upon input stream or database connection failure
|
||||||
|
*/
|
||||||
|
public long copyIn(String sql, ByteStreamWriter from)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
CopyIn cp = copyIn(sql);
|
||||||
|
try {
|
||||||
|
cp.writeToCopy(from);
|
||||||
|
return cp.endCopy();
|
||||||
|
} finally { // see to it that we do not leave the connection locked
|
||||||
|
if (cp.isActive()) {
|
||||||
|
cp.cancelCopy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
pgjdbc/src/main/java/org/postgresql/copy/CopyOperation.java
Normal file
51
pgjdbc/src/main/java/org/postgresql/copy/CopyOperation.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange bulk data between client and PostgreSQL database tables. See CopyIn and CopyOut for full
|
||||||
|
* interfaces for corresponding copy directions.
|
||||||
|
*/
|
||||||
|
public interface CopyOperation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return number of fields in each row for this operation
|
||||||
|
*/
|
||||||
|
int getFieldCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return overall format of each row: 0 = textual, 1 = binary
|
||||||
|
*/
|
||||||
|
int getFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param field number of field (0..fieldCount()-1)
|
||||||
|
* @return format of requested field: 0 = textual, 1 = binary
|
||||||
|
*/
|
||||||
|
int getFieldFormat(int field);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return is connection reserved for this Copy operation?
|
||||||
|
*/
|
||||||
|
boolean isActive();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels this copy operation, discarding any exchanged data.
|
||||||
|
*
|
||||||
|
* @throws SQLException if cancelling fails
|
||||||
|
*/
|
||||||
|
void cancelCopy() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After successful end of copy, returns the number of database records handled in that operation.
|
||||||
|
* Only implemented in PostgreSQL server version 8.2 and up. Otherwise, returns -1.
|
||||||
|
*
|
||||||
|
* @return number of handled rows or -1
|
||||||
|
*/
|
||||||
|
long getHandledRowCount();
|
||||||
|
}
|
29
pgjdbc/src/main/java/org/postgresql/copy/CopyOut.java
Normal file
29
pgjdbc/src/main/java/org/postgresql/copy/CopyOut.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public interface CopyOut extends CopyOperation {
|
||||||
|
/**
|
||||||
|
* Blocks wait for a row of data to be received from server on an active copy operation.
|
||||||
|
*
|
||||||
|
* @return byte array received from server, null if server complete copy operation
|
||||||
|
* @throws SQLException if something goes wrong for example socket timeout
|
||||||
|
*/
|
||||||
|
byte [] readFromCopy() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a row of data to be received from server on an active copy operation.
|
||||||
|
*
|
||||||
|
* @param block {@code true} if need wait data from server otherwise {@code false} and will read
|
||||||
|
* pending message from server
|
||||||
|
* @return byte array received from server, if pending message from server absent and use no
|
||||||
|
* blocking mode return null
|
||||||
|
* @throws SQLException if something goes wrong for example socket timeout
|
||||||
|
*/
|
||||||
|
byte [] readFromCopy(boolean block) throws SQLException;
|
||||||
|
}
|
178
pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java
Normal file
178
pgjdbc/src/main/java/org/postgresql/copy/PGCopyInputStream.java
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import org.postgresql.PGConnection;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InputStream for reading from a PostgreSQL COPY TO STDOUT operation.
|
||||||
|
*/
|
||||||
|
public class PGCopyInputStream extends InputStream implements CopyOut {
|
||||||
|
private CopyOut op;
|
||||||
|
private byte [] buf;
|
||||||
|
private int at;
|
||||||
|
private int len;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses given connection for specified COPY TO STDOUT operation.
|
||||||
|
*
|
||||||
|
* @param connection database connection to use for copying (protocol version 3 required)
|
||||||
|
* @param sql COPY TO STDOUT statement
|
||||||
|
* @throws SQLException if initializing the operation fails
|
||||||
|
*/
|
||||||
|
public PGCopyInputStream(PGConnection connection, String sql) throws SQLException {
|
||||||
|
this(connection.getCopyAPI().copyOut(sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use given CopyOut operation for reading.
|
||||||
|
*
|
||||||
|
* @param op COPY TO STDOUT operation
|
||||||
|
*/
|
||||||
|
public PGCopyInputStream(CopyOut op) {
|
||||||
|
this.op = op;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CopyOut getOp() {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte [] fillBuffer() throws IOException {
|
||||||
|
if (at >= len) {
|
||||||
|
try {
|
||||||
|
buf = getOp().readFromCopy();
|
||||||
|
} catch (SQLException sqle) {
|
||||||
|
throw new IOException(GT.tr("Copying from database failed: {0}", sqle.getMessage()), sqle);
|
||||||
|
}
|
||||||
|
if (buf == null) {
|
||||||
|
at = -1;
|
||||||
|
} else {
|
||||||
|
at = 0;
|
||||||
|
len = buf.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClosed() throws IOException {
|
||||||
|
if (op == null) {
|
||||||
|
throw new IOException(GT.tr("This copy stream is closed."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
return buf != null ? len - at : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
byte[] buf = fillBuffer();
|
||||||
|
return buf != null ? (buf[at++] & 0xFF) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buf) throws IOException {
|
||||||
|
return read(buf, 0, buf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] buf, int off, int siz) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
int got = 0;
|
||||||
|
byte[] data = fillBuffer();
|
||||||
|
for (; got < siz && data != null; data = fillBuffer()) {
|
||||||
|
int length = Math.min(siz - got, len - at);
|
||||||
|
System.arraycopy(data, at, buf, off + got, length);
|
||||||
|
at += length;
|
||||||
|
got += length;
|
||||||
|
}
|
||||||
|
return got == 0 && data == null ? -1 : got;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy() throws SQLException {
|
||||||
|
byte[] result = null;
|
||||||
|
try {
|
||||||
|
byte[] buf = fillBuffer();
|
||||||
|
if (buf != null) {
|
||||||
|
if (at > 0 || len < buf.length) {
|
||||||
|
result = Arrays.copyOfRange(buf, at, len);
|
||||||
|
} else {
|
||||||
|
result = buf;
|
||||||
|
}
|
||||||
|
// Mark the buffer as fully read
|
||||||
|
at = len;
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new PSQLException(GT.tr("Read from copy failed."), PSQLState.CONNECTION_FAILURE, ioe);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy(boolean block) throws SQLException {
|
||||||
|
return readFromCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// Don't complain about a double close.
|
||||||
|
CopyOut op = this.op;
|
||||||
|
if (op == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.isActive()) {
|
||||||
|
try {
|
||||||
|
op.cancelCopy();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
throw new IOException("Failed to close copy reader.", se);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.op = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelCopy() throws SQLException {
|
||||||
|
getOp().cancelCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFormat() {
|
||||||
|
return getOp().getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldFormat(int field) {
|
||||||
|
return getOp().getFieldFormat(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldCount() {
|
||||||
|
return getOp().getFieldCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return op != null && op.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getHandledRowCount() {
|
||||||
|
return getOp().getHandledRowCount();
|
||||||
|
}
|
||||||
|
}
|
203
pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
Normal file
203
pgjdbc/src/main/java/org/postgresql/copy/PGCopyOutputStream.java
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.copy;
|
||||||
|
|
||||||
|
import org.postgresql.PGConnection;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OutputStream for buffered input into a PostgreSQL COPY FROM STDIN operation.
|
||||||
|
*/
|
||||||
|
public class PGCopyOutputStream extends OutputStream implements CopyIn {
|
||||||
|
private CopyIn op;
|
||||||
|
private final byte[] copyBuffer;
|
||||||
|
private final byte[] singleByteBuffer = new byte[1];
|
||||||
|
private int at;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses given connection for specified COPY FROM STDIN operation.
|
||||||
|
*
|
||||||
|
* @param connection database connection to use for copying (protocol version 3 required)
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @throws SQLException if initializing the operation fails
|
||||||
|
*/
|
||||||
|
public PGCopyOutputStream(PGConnection connection, String sql) throws SQLException {
|
||||||
|
this(connection, sql, CopyManager.DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses given connection for specified COPY FROM STDIN operation.
|
||||||
|
*
|
||||||
|
* @param connection database connection to use for copying (protocol version 3 required)
|
||||||
|
* @param sql COPY FROM STDIN statement
|
||||||
|
* @param bufferSize try to send this many bytes at a time
|
||||||
|
* @throws SQLException if initializing the operation fails
|
||||||
|
*/
|
||||||
|
public PGCopyOutputStream(PGConnection connection, String sql, int bufferSize)
|
||||||
|
throws SQLException {
|
||||||
|
this(connection.getCopyAPI().copyIn(sql), bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use given CopyIn operation for writing.
|
||||||
|
*
|
||||||
|
* @param op COPY FROM STDIN operation
|
||||||
|
*/
|
||||||
|
public PGCopyOutputStream(CopyIn op) {
|
||||||
|
this(op, CopyManager.DEFAULT_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use given CopyIn operation for writing.
|
||||||
|
*
|
||||||
|
* @param op COPY FROM STDIN operation
|
||||||
|
* @param bufferSize try to send this many bytes at a time
|
||||||
|
*/
|
||||||
|
public PGCopyOutputStream(CopyIn op, int bufferSize) {
|
||||||
|
this.op = op;
|
||||||
|
copyBuffer = new byte[bufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
private CopyIn getOp() {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
if (b < 0 || b > 255) {
|
||||||
|
throw new IOException(GT.tr("Cannot write to copy a byte of value {0}", b));
|
||||||
|
}
|
||||||
|
singleByteBuffer[0] = (byte) b;
|
||||||
|
write(singleByteBuffer, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buf) throws IOException {
|
||||||
|
write(buf, 0, buf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buf, int off, int siz) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
try {
|
||||||
|
writeToCopy(buf, off, siz);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
throw new IOException("Write to copy failed.", se);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkClosed() throws IOException {
|
||||||
|
if (op == null) {
|
||||||
|
throw new IOException(GT.tr("This copy stream is closed."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// Don't complain about a double close.
|
||||||
|
CopyIn op = this.op;
|
||||||
|
if (op == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.isActive()) {
|
||||||
|
try {
|
||||||
|
endCopy();
|
||||||
|
} catch (SQLException se) {
|
||||||
|
throw new IOException("Ending write to copy failed.", se);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.op = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
try {
|
||||||
|
getOp().writeToCopy(copyBuffer, 0, at);
|
||||||
|
at = 0;
|
||||||
|
getOp().flushCopy();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new IOException("Unable to flush stream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(byte[] buf, int off, int siz) throws SQLException {
|
||||||
|
if (at > 0
|
||||||
|
&& siz > copyBuffer.length - at) { // would not fit into rest of our buf, so flush buf
|
||||||
|
getOp().writeToCopy(copyBuffer, 0, at);
|
||||||
|
at = 0;
|
||||||
|
}
|
||||||
|
if (siz > copyBuffer.length) { // would still not fit into buf, so just pass it through
|
||||||
|
getOp().writeToCopy(buf, off, siz);
|
||||||
|
} else { // fits into our buf, so save it there
|
||||||
|
System.arraycopy(buf, off, copyBuffer, at, siz);
|
||||||
|
at += siz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(ByteStreamWriter from) throws SQLException {
|
||||||
|
if (at > 0) {
|
||||||
|
// flush existing buffer so order is preserved
|
||||||
|
getOp().writeToCopy(copyBuffer, 0, at);
|
||||||
|
at = 0;
|
||||||
|
}
|
||||||
|
getOp().writeToCopy(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFormat() {
|
||||||
|
return getOp().getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldFormat(int field) {
|
||||||
|
return getOp().getFieldFormat(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelCopy() throws SQLException {
|
||||||
|
getOp().cancelCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldCount() {
|
||||||
|
return getOp().getFieldCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return op != null && getOp().isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushCopy() throws SQLException {
|
||||||
|
getOp().flushCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long endCopy() throws SQLException {
|
||||||
|
if (at > 0) {
|
||||||
|
getOp().writeToCopy(copyBuffer, 0, at);
|
||||||
|
}
|
||||||
|
getOp().endCopy();
|
||||||
|
return getHandledRowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getHandledRowCount() {
|
||||||
|
return getOp().getHandledRowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the canonicalization/interning of {@code String} instances which contain only ascii characters,
|
||||||
|
* keyed by the {@code byte[]} representation (in ascii).
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The values are stored in {@link SoftReference}s, allowing them to be garbage collected if not in use and there is
|
||||||
|
* memory pressure.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b> Instances are safe for concurrent use.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Brett Okken
|
||||||
|
*/
|
||||||
|
final class AsciiStringInterner {
|
||||||
|
|
||||||
|
private abstract static class BaseKey {
|
||||||
|
private final int hash;
|
||||||
|
|
||||||
|
BaseKey(int hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int hashCode() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean equals(Object obj) {
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof BaseKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final BaseKey other = (BaseKey) obj;
|
||||||
|
return equalsBytes(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean equalsBytes(BaseKey other);
|
||||||
|
|
||||||
|
abstract boolean equals(byte[] other, int offset, int length);
|
||||||
|
|
||||||
|
abstract void appendString(StringBuilder sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only used for lookups, never to actually store entries.
|
||||||
|
*/
|
||||||
|
private static class TempKey extends BaseKey {
|
||||||
|
final byte[] bytes;
|
||||||
|
final int offset;
|
||||||
|
final int length;
|
||||||
|
|
||||||
|
TempKey(int hash, byte[] bytes, int offset, int length) {
|
||||||
|
super(hash);
|
||||||
|
this.bytes = bytes;
|
||||||
|
this.offset = offset;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean equalsBytes(BaseKey other) {
|
||||||
|
return other.equals(bytes, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(byte[] other, int offset, int length) {
|
||||||
|
return arrayEquals(this.bytes, this.offset, this.length, other, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void appendString(StringBuilder sb) {
|
||||||
|
for (int i = offset, j = offset + length; i < j; i++) {
|
||||||
|
sb.append((char) bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance used for inserting values into the cache. The {@code byte[]} must be a copy
|
||||||
|
* that will never be mutated.
|
||||||
|
*/
|
||||||
|
private static final class Key extends BaseKey {
|
||||||
|
final byte[] key;
|
||||||
|
|
||||||
|
Key(byte[] key, int hash) {
|
||||||
|
super(hash);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
boolean equalsBytes(BaseKey other) {
|
||||||
|
return other.equals(key, 0, key.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(byte[] other, int offset, int length) {
|
||||||
|
return arrayEquals(this.key, 0, this.key.length, other, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void appendString(StringBuilder sb) {
|
||||||
|
for (int i = 0; i < key.length; i++) {
|
||||||
|
sb.append((char) key[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom {@link SoftReference} implementation which maintains a reference to the key in the cache,
|
||||||
|
* which allows aggressive cleaning when garbage collector collects the {@code String} instance.
|
||||||
|
*/
|
||||||
|
private final class StringReference extends SoftReference<String> {
|
||||||
|
|
||||||
|
private final BaseKey key;
|
||||||
|
|
||||||
|
StringReference(BaseKey key, String referent) {
|
||||||
|
super(referent, refQueue);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
cache.remove(key, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the canonicalized values, keyed by the ascii {@code byte[]}.
|
||||||
|
*/
|
||||||
|
final ConcurrentMap<BaseKey, SoftReference<String>> cache = new ConcurrentHashMap<>(128);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for {@link Reference} as values in {@code cache}.
|
||||||
|
*/
|
||||||
|
final ReferenceQueue<String> refQueue = new ReferenceQueue<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preemptively populates a value into the cache. This is intended to be used with {@code String} constants
|
||||||
|
* which are frequently used. While this can work with other {@code String} values, if <i>val</i> is ever
|
||||||
|
* garbage collected, it will not be actively removed from this instance.
|
||||||
|
*
|
||||||
|
* @param val The value to intern. Must not be {@code null}.
|
||||||
|
* @return Indication if <i>val</i> is an ascii String and placed into cache.
|
||||||
|
*/
|
||||||
|
public boolean putString(String val) {
|
||||||
|
//ask for utf-8 so that we can detect if any of the characters are not ascii
|
||||||
|
final byte[] copy = val.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final int hash = hashKey(copy, 0, copy.length);
|
||||||
|
if (hash == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Key key = new Key(copy, hash);
|
||||||
|
//we are assuming this is a java interned string from , so this is unlikely to ever be
|
||||||
|
//reclaimed. so there is no value in using the custom StringReference or hand off to
|
||||||
|
//the refQueue.
|
||||||
|
//on the outside chance it actually does get reclaimed, it will just hang around as an
|
||||||
|
//empty reference in the map unless/until attempted to be retrieved
|
||||||
|
cache.put(key, new SoftReference<String>(val));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a {@link String} instance for the given <i>bytes</i>. If all are valid ascii (i.e. {@code >= 0})
|
||||||
|
* either an existing value will be returned, or the newly created {@code String} will be stored before being
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If non-ascii bytes are discovered, the <i>encoding</i> will be used to
|
||||||
|
* {@link Encoding#decode(byte[], int, int) decode} and that value will be returned (but not stored).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param bytes The bytes of the String. Must not be {@code null}.
|
||||||
|
* @param offset Offset into <i>bytes</i> to start.
|
||||||
|
* @param length The number of bytes in <i>bytes</i> which are relevant.
|
||||||
|
* @param encoding To use if non-ascii bytes seen.
|
||||||
|
* @return Decoded {@code String} from <i>bytes</i>.
|
||||||
|
* @throws IOException If error decoding from <i>Encoding</i>.
|
||||||
|
*/
|
||||||
|
public String getString(byte[] bytes, int offset, int length, Encoding encoding) throws IOException {
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final int hash = hashKey(bytes, offset, length);
|
||||||
|
// 0 indicates the presence of a non-ascii character - defer to encoding to create the string
|
||||||
|
if (hash == 0) {
|
||||||
|
return encoding.decode(bytes, offset, length);
|
||||||
|
}
|
||||||
|
cleanQueue();
|
||||||
|
// create a TempKey with the byte[] given
|
||||||
|
final TempKey tempKey = new TempKey(hash, bytes, offset, length);
|
||||||
|
SoftReference<String> ref = cache.get(tempKey);
|
||||||
|
if (ref != null) {
|
||||||
|
final String val = ref.get();
|
||||||
|
if (val != null) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// in order to insert we need to create a "real" key with copy of bytes that will not be changed
|
||||||
|
final byte[] copy = Arrays.copyOfRange(bytes, offset, offset + length);
|
||||||
|
final Key key = new Key(copy, hash);
|
||||||
|
final String value = new String(copy, StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
|
// handle case where a concurrent thread has populated the map or existing value has cleared reference
|
||||||
|
ref = cache.compute(key, (k, v) -> {
|
||||||
|
if (v == null) {
|
||||||
|
return new StringReference(key, value);
|
||||||
|
}
|
||||||
|
final String val = v.get();
|
||||||
|
return val != null ? v : new StringReference(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ref.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a {@link String} instance for the given <i>bytes</i>.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If all are valid ascii (i.e. {@code >= 0}) and a corresponding {@code String} value exists, it
|
||||||
|
* will be returned. If no value exists, a {@code String} will be created, but not stored.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If non-ascii bytes are discovered, the <i>encoding</i> will be used to
|
||||||
|
* {@link Encoding#decode(byte[], int, int) decode} and that value will be returned (but not stored).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param bytes The bytes of the String. Must not be {@code null}.
|
||||||
|
* @param offset Offset into <i>bytes</i> to start.
|
||||||
|
* @param length The number of bytes in <i>bytes</i> which are relevant.
|
||||||
|
* @param encoding To use if non-ascii bytes seen.
|
||||||
|
* @return Decoded {@code String} from <i>bytes</i>.
|
||||||
|
* @throws IOException If error decoding from <i>Encoding</i>.
|
||||||
|
*/
|
||||||
|
public String getStringIfPresent(byte[] bytes, int offset, int length, Encoding encoding) throws IOException {
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final int hash = hashKey(bytes, offset, length);
|
||||||
|
// 0 indicates the presence of a non-ascii character - defer to encoding to create the string
|
||||||
|
if (hash == 0) {
|
||||||
|
return encoding.decode(bytes, offset, length);
|
||||||
|
}
|
||||||
|
cleanQueue();
|
||||||
|
// create a TempKey with the byte[] given
|
||||||
|
final TempKey tempKey = new TempKey(hash, bytes, offset, length);
|
||||||
|
SoftReference<String> ref = cache.get(tempKey);
|
||||||
|
if (ref != null) {
|
||||||
|
final String val = ref.get();
|
||||||
|
if (val != null) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes, offset, length, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process any entries in {@link #refQueue} to purge from the {@link #cache}.
|
||||||
|
* @see StringReference#dispose()
|
||||||
|
*/
|
||||||
|
private void cleanQueue() {
|
||||||
|
Reference<?> ref;
|
||||||
|
while ((ref = refQueue.poll()) != null) {
|
||||||
|
((StringReference) ref).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a hash value for the relevant entries in <i>bytes</i> as long as all values are ascii ({@code >= 0}).
|
||||||
|
* @return hash code for relevant bytes, or {@code 0} if non-ascii bytes present.
|
||||||
|
*/
|
||||||
|
private static int hashKey(byte[] bytes, int offset, int length) {
|
||||||
|
int result = 1;
|
||||||
|
for (int i = offset, j = offset + length; i < j; i++) {
|
||||||
|
final byte b = bytes[i];
|
||||||
|
// bytes are signed values. all ascii values are positive
|
||||||
|
if (b < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
result = 31 * result + b;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs equality check between <i>a</i> and <i>b</i> (with corresponding offset/length values).
|
||||||
|
* <p>
|
||||||
|
* The {@code static boolean equals(byte[].class, int, int, byte[], int, int} method in {@link java.util.Arrays}
|
||||||
|
* is optimized for longer {@code byte[]} instances than is expected to be seen here.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
static boolean arrayEquals(byte[] a, int aOffset, int aLength, byte[] b, int bOffset, int bLength) {
|
||||||
|
if (aLength != bLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//TODO: in jdk9, could use VarHandle to read 4 bytes at a time as an int for comparison
|
||||||
|
// or 8 bytes as a long - though we likely expect short values here
|
||||||
|
for (int i = 0; i < aLength; i++) {
|
||||||
|
if (a[aOffset + i] != b[bOffset + i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder(32 + (8 * cache.size()));
|
||||||
|
sb.append("AsciiStringInterner [");
|
||||||
|
cache.forEach((k, v) -> {
|
||||||
|
sb.append('\'');
|
||||||
|
k.appendString(sb);
|
||||||
|
sb.append("', ");
|
||||||
|
});
|
||||||
|
//replace trailing ', ' with ']';
|
||||||
|
final int length = sb.length();
|
||||||
|
if (length > 21) {
|
||||||
|
sb.setLength(sb.length() - 2);
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
236
pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
Normal file
236
pgjdbc/src/main/java/org/postgresql/core/BaseConnection.java
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGConnection;
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.jdbc.FieldMetadata;
|
||||||
|
import org.postgresql.jdbc.TimestampUtils;
|
||||||
|
import org.postgresql.util.LruCache;
|
||||||
|
import org.postgresql.xml.PGXmlFactoryFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Driver-internal connection interface. Application code should not use this interface.
|
||||||
|
*/
|
||||||
|
public interface BaseConnection extends PGConnection, Connection {
|
||||||
|
/**
|
||||||
|
* Cancel the current query executing on this connection.
|
||||||
|
*
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void cancelQuery() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query that returns a single resultset. Never causes a new transaction to be
|
||||||
|
* started regardless of the autocommit setting.
|
||||||
|
*
|
||||||
|
* @param s the query to execute
|
||||||
|
* @return the (non-null) returned resultset
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
ResultSet execSQLQuery(String s) throws SQLException;
|
||||||
|
|
||||||
|
ResultSet execSQLQuery(String s, int resultSetType, int resultSetConcurrency)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query that does not return results. Never causes a new transaction to be started
|
||||||
|
* regardless of the autocommit setting.
|
||||||
|
*
|
||||||
|
* @param s the query to execute
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
void execSQLUpdate(String s) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the QueryExecutor implementation for this connection.
|
||||||
|
*
|
||||||
|
* @return the (non-null) executor
|
||||||
|
*/
|
||||||
|
QueryExecutor getQueryExecutor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal protocol for work with physical and logical replication. Physical replication available
|
||||||
|
* only since PostgreSQL version 9.1. Logical replication available only since PostgreSQL version 9.4.
|
||||||
|
*
|
||||||
|
* @return not null replication protocol
|
||||||
|
*/
|
||||||
|
ReplicationProtocol getReplicationProtocol();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Construct and return an appropriate object for the given type and value. This only considers
|
||||||
|
* the types registered via {@link org.postgresql.PGConnection#addDataType(String, Class)} and
|
||||||
|
* {@link org.postgresql.PGConnection#addDataType(String, String)}.</p>
|
||||||
|
*
|
||||||
|
* <p>If no class is registered as handling the given type, then a generic
|
||||||
|
* {@link org.postgresql.util.PGobject} instance is returned.</p>
|
||||||
|
*
|
||||||
|
* <p>value or byteValue must be non-null</p>
|
||||||
|
* @param type the backend typename
|
||||||
|
* @param value the type-specific string representation of the value
|
||||||
|
* @param byteValue the type-specific binary representation of the value
|
||||||
|
* @return an appropriate object; never null.
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
Object getObject(String type, String value, byte [] byteValue)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
Encoding getEncoding() throws SQLException;
|
||||||
|
|
||||||
|
TypeInfo getTypeInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Check if we have at least a particular server version.</p>
|
||||||
|
*
|
||||||
|
* <p>The input version is of the form xxyyzz, matching a PostgreSQL version like xx.yy.zz. So 9.0.12
|
||||||
|
* is 90012.</p>
|
||||||
|
*
|
||||||
|
* @param ver the server version to check, of the form xxyyzz eg 90401
|
||||||
|
* @return true if the server version is at least "ver".
|
||||||
|
*/
|
||||||
|
boolean haveMinimumServerVersion(int ver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Check if we have at least a particular server version.</p>
|
||||||
|
*
|
||||||
|
* <p>The input version is of the form xxyyzz, matching a PostgreSQL version like xx.yy.zz. So 9.0.12
|
||||||
|
* is 90012.</p>
|
||||||
|
*
|
||||||
|
* @param ver the server version to check
|
||||||
|
* @return true if the server version is at least "ver".
|
||||||
|
*/
|
||||||
|
boolean haveMinimumServerVersion(Version ver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a string using the database's client_encoding (usually UTF8, but can vary on older
|
||||||
|
* server versions). This is used when constructing synthetic resultsets (for example, in metadata
|
||||||
|
* methods).
|
||||||
|
*
|
||||||
|
* @param str the string to encode
|
||||||
|
* @return an encoded representation of the string
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
byte[] encodeString(String str) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a string for use as string-literal within an SQL command. The method chooses the
|
||||||
|
* applicable escaping rules based on the value of {@link #getStandardConformingStrings()}.
|
||||||
|
*
|
||||||
|
* @param str a string value
|
||||||
|
* @return the escaped representation of the string
|
||||||
|
* @throws SQLException if the string contains a {@code \0} character
|
||||||
|
*/
|
||||||
|
String escapeString(String str) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the server treats string-literals according to the SQL standard or if it uses
|
||||||
|
* traditional PostgreSQL escaping rules. Versions up to 8.1 always treated backslashes as escape
|
||||||
|
* characters in string-literals. Since 8.2, this depends on the value of the
|
||||||
|
* {@code standard_conforming_strings} server variable.
|
||||||
|
*
|
||||||
|
* @return true if the server treats string literals according to the SQL standard
|
||||||
|
* @see QueryExecutor#getStandardConformingStrings()
|
||||||
|
*/
|
||||||
|
boolean getStandardConformingStrings();
|
||||||
|
|
||||||
|
// Ew. Quick hack to give access to the connection-specific utils implementation.
|
||||||
|
@Deprecated
|
||||||
|
TimestampUtils getTimestampUtils();
|
||||||
|
|
||||||
|
// Get the per-connection logger.
|
||||||
|
Logger getLogger();
|
||||||
|
|
||||||
|
// Get the bind-string-as-varchar config flag
|
||||||
|
boolean getStringVarcharFlag();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current transaction state of this connection.
|
||||||
|
*
|
||||||
|
* @return current transaction state of this connection
|
||||||
|
*/
|
||||||
|
TransactionState getTransactionState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if value for the given oid should be sent using binary transfer. False if value
|
||||||
|
* should be sent using text transfer.
|
||||||
|
*
|
||||||
|
* @param oid The oid to check.
|
||||||
|
* @return True for binary transfer, false for text transfer.
|
||||||
|
*/
|
||||||
|
boolean binaryTransferSend(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether to disable column name sanitation.
|
||||||
|
*
|
||||||
|
* @return true column sanitizer is disabled
|
||||||
|
*/
|
||||||
|
boolean isColumnSanitiserDisabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a TimerTask for later execution. The task will be scheduled with the shared Timer for
|
||||||
|
* this connection.
|
||||||
|
*
|
||||||
|
* @param timerTask timer task to schedule
|
||||||
|
* @param milliSeconds delay in milliseconds
|
||||||
|
*/
|
||||||
|
void addTimerTask(TimerTask timerTask, long milliSeconds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke purge() on the underlying shared Timer so that internal resources will be released.
|
||||||
|
*/
|
||||||
|
void purgeTimerTasks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return metadata cache for given connection.
|
||||||
|
*
|
||||||
|
* @return metadata cache
|
||||||
|
*/
|
||||||
|
LruCache<FieldMetadata.Key, FieldMetadata> getFieldMetadataCache();
|
||||||
|
|
||||||
|
CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParameterized,
|
||||||
|
String... columnNames)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, the connection resets statement cache in case deallocate all/discard all
|
||||||
|
* message is observed.
|
||||||
|
* This API allows to disable that feature for testing purposes.
|
||||||
|
*
|
||||||
|
* @param flushCacheOnDeallocate true if statement cache should be reset when "deallocate/discard" message observed
|
||||||
|
*/
|
||||||
|
void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if statements to backend should be hinted as read only.
|
||||||
|
*
|
||||||
|
* @return Indication if hints to backend (such as when transaction begins)
|
||||||
|
* should be read only.
|
||||||
|
* @see PGProperty#READ_ONLY_MODE
|
||||||
|
*/
|
||||||
|
boolean hintReadOnly();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the factory to instantiate XML processing factories.
|
||||||
|
*
|
||||||
|
* @return The factory to use to instantiate XML processing factories
|
||||||
|
* @throws SQLException if the class cannot be found or instantiated.
|
||||||
|
*/
|
||||||
|
PGXmlFactoryFactory getXmlFactoryFactory() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if error details from server used in included in logging and exceptions.
|
||||||
|
*
|
||||||
|
* @return true if should be included and passed on to other exceptions
|
||||||
|
*/
|
||||||
|
boolean getLogServerErrorDetail();
|
||||||
|
}
|
72
pgjdbc/src/main/java/org/postgresql/core/BaseQueryKey.java
Normal file
72
pgjdbc/src/main/java/org/postgresql/core/BaseQueryKey.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.CanEstimateSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used as a cache key for simple statements that have no "returning columns".
|
||||||
|
* Prepared statements that have no returning columns use just {@code String sql} as a key.
|
||||||
|
* Simple and Prepared statements that have returning columns use {@link QueryWithReturningColumnsKey}
|
||||||
|
* as a cache key.
|
||||||
|
*/
|
||||||
|
class BaseQueryKey implements CanEstimateSize {
|
||||||
|
public final String sql;
|
||||||
|
public final boolean isParameterized;
|
||||||
|
public final boolean escapeProcessing;
|
||||||
|
|
||||||
|
BaseQueryKey(String sql, boolean isParameterized, boolean escapeProcessing) {
|
||||||
|
this.sql = sql;
|
||||||
|
this.isParameterized = isParameterized;
|
||||||
|
this.escapeProcessing = escapeProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BaseQueryKey{"
|
||||||
|
+ "sql='" + sql + '\''
|
||||||
|
+ ", isParameterized=" + isParameterized
|
||||||
|
+ ", escapeProcessing=" + escapeProcessing
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
if (sql == null) { // just in case
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
return 16 + sql.length() * 2L; // 2 bytes per char, revise with Java 9's compact strings
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseQueryKey that = (BaseQueryKey) o;
|
||||||
|
|
||||||
|
if (isParameterized != that.isParameterized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (escapeProcessing != that.escapeProcessing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sql != null ? sql.equals(that.sql) : that.sql == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = sql != null ? sql.hashCode() : 0;
|
||||||
|
result = 31 * result + (isParameterized ? 1 : 0);
|
||||||
|
result = 31 * result + (escapeProcessing ? 1 : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
75
pgjdbc/src/main/java/org/postgresql/core/BaseStatement.java
Normal file
75
pgjdbc/src/main/java/org/postgresql/core/BaseStatement.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGStatement;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Driver-internal statement interface. Application code should not use this interface.
|
||||||
|
*/
|
||||||
|
public interface BaseStatement extends PGStatement, Statement {
|
||||||
|
/**
|
||||||
|
* Create a synthetic resultset from data provided by the driver.
|
||||||
|
*
|
||||||
|
* @param fields the column metadata for the resultset
|
||||||
|
* @param tuples the resultset data
|
||||||
|
* @return the new ResultSet
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
ResultSet createDriverResultSet(Field[] fields, List<Tuple> tuples) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resultset from data retrieved from the server.
|
||||||
|
*
|
||||||
|
* @param originalQuery the query that generated this resultset; used when dealing with updateable
|
||||||
|
* resultsets
|
||||||
|
* @param fields the column metadata for the resultset
|
||||||
|
* @param tuples the resultset data
|
||||||
|
* @param cursor the cursor to use to retrieve more data from the server; if null, no additional
|
||||||
|
* data is present.
|
||||||
|
* @return the new ResultSet
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
ResultSet createResultSet(Query originalQuery, Field[] fields, List<Tuple> tuples,
|
||||||
|
ResultCursor cursor) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query, passing additional query flags.
|
||||||
|
*
|
||||||
|
* @param sql the query to execute (JDBC-style query)
|
||||||
|
* @param flags additional {@link QueryExecutor} flags for execution; these are bitwise-ORed into
|
||||||
|
* the default flags.
|
||||||
|
* @return true if there is a result set
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
boolean executeWithFlags(String sql, int flags) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query, passing additional query flags.
|
||||||
|
*
|
||||||
|
* @param cachedQuery the query to execute (native to PostgreSQL)
|
||||||
|
* @param flags additional {@link QueryExecutor} flags for execution; these are bitwise-ORed into
|
||||||
|
* the default flags.
|
||||||
|
* @return true if there is a result set
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
boolean executeWithFlags(CachedQuery cachedQuery, int flags) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a prepared query, passing additional query flags.
|
||||||
|
*
|
||||||
|
* @param flags additional {@link QueryExecutor} flags for execution; these are bitwise-ORed into
|
||||||
|
* the default flags.
|
||||||
|
* @return true if there is a result set
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
boolean executeWithFlags(int flags) throws SQLException;
|
||||||
|
}
|
75
pgjdbc/src/main/java/org/postgresql/core/CachedQuery.java
Normal file
75
pgjdbc/src/main/java/org/postgresql/core/CachedQuery.java
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.CanEstimateSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores information on the parsed JDBC query. It is used to cut parsing overhead when executing
|
||||||
|
* the same query through {@link java.sql.Connection#prepareStatement(String)}.
|
||||||
|
*/
|
||||||
|
public class CachedQuery implements CanEstimateSize {
|
||||||
|
/**
|
||||||
|
* Cache key. {@link String} or {@code org.postgresql.util.CanEstimateSize}.
|
||||||
|
*/
|
||||||
|
public final Object key;
|
||||||
|
public final Query query;
|
||||||
|
public final boolean isFunction;
|
||||||
|
|
||||||
|
private int executeCount;
|
||||||
|
|
||||||
|
public CachedQuery(Object key, Query query, boolean isFunction) {
|
||||||
|
assert key instanceof String || key instanceof CanEstimateSize
|
||||||
|
: "CachedQuery.key should either be String or implement CanEstimateSize."
|
||||||
|
+ " Actual class is " + key.getClass();
|
||||||
|
this.key = key;
|
||||||
|
this.query = query;
|
||||||
|
this.isFunction = isFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increaseExecuteCount() {
|
||||||
|
if (executeCount < Integer.MAX_VALUE) {
|
||||||
|
executeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increaseExecuteCount(int inc) {
|
||||||
|
int newValue = executeCount + inc;
|
||||||
|
if (newValue > 0) { // if overflows, just ignore the update
|
||||||
|
executeCount = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times this statement has been used.
|
||||||
|
*
|
||||||
|
* @return number of times this statement has been used
|
||||||
|
*/
|
||||||
|
public int getExecuteCount() {
|
||||||
|
return executeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
long queryLength;
|
||||||
|
if (key instanceof String) {
|
||||||
|
queryLength = ((String) key).length() * 2L; // 2 bytes per char, revise with Java 9's compact strings
|
||||||
|
} else {
|
||||||
|
queryLength = ((CanEstimateSize) key).getSize();
|
||||||
|
}
|
||||||
|
return queryLength * 2 /* original query and native sql */
|
||||||
|
+ 100L /* entry in hash map, CachedQuery wrapper, etc */;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CachedQuery{"
|
||||||
|
+ "executeCount=" + executeCount
|
||||||
|
+ ", query=" + query
|
||||||
|
+ ", isFunction=" + isFunction
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.jdbc.PreferQueryMode;
|
||||||
|
import org.postgresql.util.LruCache;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of {@link CachedQuery} for a given connection.
|
||||||
|
*/
|
||||||
|
class CachedQueryCreateAction implements LruCache.CreateAction<Object, CachedQuery> {
|
||||||
|
private static final String[] EMPTY_RETURNING = new String[0];
|
||||||
|
private final QueryExecutor queryExecutor;
|
||||||
|
|
||||||
|
CachedQueryCreateAction(QueryExecutor queryExecutor) {
|
||||||
|
this.queryExecutor = queryExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedQuery create(Object key) throws SQLException {
|
||||||
|
assert key instanceof String || key instanceof BaseQueryKey
|
||||||
|
: "Query key should be String or BaseQueryKey. Given " + key.getClass() + ", sql: "
|
||||||
|
+ key;
|
||||||
|
BaseQueryKey queryKey;
|
||||||
|
String parsedSql;
|
||||||
|
if (key instanceof BaseQueryKey) {
|
||||||
|
queryKey = (BaseQueryKey) key;
|
||||||
|
parsedSql = queryKey.sql;
|
||||||
|
} else {
|
||||||
|
queryKey = null;
|
||||||
|
parsedSql = (String) key;
|
||||||
|
}
|
||||||
|
if (key instanceof String || queryKey.escapeProcessing) {
|
||||||
|
parsedSql =
|
||||||
|
Parser.replaceProcessing(parsedSql, true, queryExecutor.getStandardConformingStrings());
|
||||||
|
}
|
||||||
|
boolean isFunction;
|
||||||
|
if (key instanceof CallableQueryKey) {
|
||||||
|
JdbcCallParseInfo callInfo =
|
||||||
|
Parser.modifyJdbcCall(parsedSql, queryExecutor.getStandardConformingStrings(),
|
||||||
|
queryExecutor.getServerVersionNum(), queryExecutor.getProtocolVersion(), queryExecutor.getEscapeSyntaxCallMode());
|
||||||
|
parsedSql = callInfo.getSql();
|
||||||
|
isFunction = callInfo.isFunction();
|
||||||
|
} else {
|
||||||
|
isFunction = false;
|
||||||
|
}
|
||||||
|
boolean isParameterized = key instanceof String || queryKey.isParameterized;
|
||||||
|
boolean splitStatements = isParameterized || queryExecutor.getPreferQueryMode().compareTo(PreferQueryMode.EXTENDED) >= 0;
|
||||||
|
|
||||||
|
String[] returningColumns;
|
||||||
|
if (key instanceof QueryWithReturningColumnsKey) {
|
||||||
|
returningColumns = ((QueryWithReturningColumnsKey) key).columnNames;
|
||||||
|
} else {
|
||||||
|
returningColumns = EMPTY_RETURNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<NativeQuery> queries = Parser.parseJdbcSql(parsedSql,
|
||||||
|
queryExecutor.getStandardConformingStrings(), isParameterized, splitStatements,
|
||||||
|
queryExecutor.isReWriteBatchedInsertsEnabled(), queryExecutor.getQuoteReturningIdentifiers(),
|
||||||
|
returningColumns
|
||||||
|
);
|
||||||
|
|
||||||
|
Query query = queryExecutor.wrap(queries);
|
||||||
|
return new CachedQuery(key, query, isFunction);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serves as a cache key for {@link java.sql.CallableStatement}.
|
||||||
|
* Callable statements require some special parsing before use (due to JDBC {@code {?= call...}}
|
||||||
|
* syntax, thus a special cache key class is used to trigger proper parsing for callable statements.
|
||||||
|
*/
|
||||||
|
class CallableQueryKey extends BaseQueryKey {
|
||||||
|
|
||||||
|
CallableQueryKey(String sql) {
|
||||||
|
super(sql, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CallableQueryKey{"
|
||||||
|
+ "sql='" + sql + '\''
|
||||||
|
+ ", isParameterized=" + isParameterized
|
||||||
|
+ ", escapeProcessing=" + escapeProcessing
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode() * 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
// Nothing interesting here, overriding equals to make hashCode and equals paired
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses {@code oid} and {@code rows} from a {@code CommandComplete (B)} message (end of Execute).
|
||||||
|
*/
|
||||||
|
public final class CommandCompleteParser {
|
||||||
|
private long oid;
|
||||||
|
private long rows;
|
||||||
|
|
||||||
|
public CommandCompleteParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOid() {
|
||||||
|
return oid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(long oid, long rows) {
|
||||||
|
this.oid = oid;
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses {@code CommandComplete (B)} message.
|
||||||
|
* Status is in the format of "COMMAND OID ROWS" where both 'OID' and 'ROWS' are optional
|
||||||
|
* and COMMAND can have spaces within it, like CREATE TABLE.
|
||||||
|
*
|
||||||
|
* @param status COMMAND OID ROWS message
|
||||||
|
* @throws PSQLException in case the status cannot be parsed
|
||||||
|
*/
|
||||||
|
public void parse(String status) throws PSQLException {
|
||||||
|
// Assumption: command neither starts nor ends with a digit
|
||||||
|
if (!Parser.isDigitAt(status, status.length() - 1)) {
|
||||||
|
set(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan backwards, while searching for a maximum of two number groups
|
||||||
|
// COMMAND OID ROWS
|
||||||
|
// COMMAND ROWS
|
||||||
|
long oid = 0;
|
||||||
|
long rows = 0;
|
||||||
|
try {
|
||||||
|
int lastSpace = status.lastIndexOf(' ');
|
||||||
|
// Status ends with a digit => it is ROWS
|
||||||
|
if (Parser.isDigitAt(status, lastSpace + 1)) {
|
||||||
|
rows = Parser.parseLong(status, lastSpace + 1, status.length());
|
||||||
|
|
||||||
|
if (Parser.isDigitAt(status, lastSpace - 1)) {
|
||||||
|
int penultimateSpace = status.lastIndexOf(' ', lastSpace - 1);
|
||||||
|
if (Parser.isDigitAt(status, penultimateSpace + 1)) {
|
||||||
|
oid = Parser.parseLong(status, penultimateSpace + 1, lastSpace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// This should only occur if the oid or rows are out of 0..Long.MAX_VALUE range
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("Unable to parse the count in command completion tag: {0}.", status),
|
||||||
|
PSQLState.CONNECTION_FAILURE, e);
|
||||||
|
}
|
||||||
|
set(oid, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CommandStatus{"
|
||||||
|
+ "oid=" + oid
|
||||||
|
+ ", rows=" + rows
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandCompleteParser that = (CommandCompleteParser) o;
|
||||||
|
|
||||||
|
if (oid != that.oid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return rows == that.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) (oid ^ (oid >>> 32));
|
||||||
|
result = 31 * result + (int) (rows ^ (rows >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.core.v3.ConnectionFactoryImpl;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles protocol-specific connection setup.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public abstract class ConnectionFactory {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ConnectionFactory.class.getName());
|
||||||
|
|
||||||
|
public ConnectionFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Establishes and initializes a new connection.</p>
|
||||||
|
*
|
||||||
|
* <p>If the "protocolVersion" property is specified, only that protocol version is tried. Otherwise,
|
||||||
|
* all protocols are tried in order, falling back to older protocols as necessary.</p>
|
||||||
|
*
|
||||||
|
* <p>Currently, protocol versions 3 (7.4+) is supported.</p>
|
||||||
|
*
|
||||||
|
* @param hostSpecs at least one host and port to connect to; multiple elements for round-robin
|
||||||
|
* failover
|
||||||
|
* @param info extra properties controlling the connection; notably, "password" if present
|
||||||
|
* supplies the password to authenticate with.
|
||||||
|
* @return the new, initialized, connection
|
||||||
|
* @throws SQLException if the connection could not be established.
|
||||||
|
*/
|
||||||
|
public static QueryExecutor openConnection(HostSpec[] hostSpecs,
|
||||||
|
Properties info) throws SQLException {
|
||||||
|
String protoName = PGProperty.PROTOCOL_VERSION.getOrDefault(info);
|
||||||
|
|
||||||
|
if (protoName == null || protoName.isEmpty() || "3".equals(protoName)) {
|
||||||
|
ConnectionFactory connectionFactory = new ConnectionFactoryImpl();
|
||||||
|
QueryExecutor queryExecutor = connectionFactory.openConnectionImpl(
|
||||||
|
hostSpecs, info);
|
||||||
|
if (queryExecutor != null) {
|
||||||
|
return queryExecutor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("A connection could not be made using the requested protocol {0}.", protoName),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link #openConnection} for a particular protocol version. Implemented by
|
||||||
|
* subclasses of {@link ConnectionFactory}.
|
||||||
|
*
|
||||||
|
* @param hostSpecs at least one host and port to connect to; multiple elements for round-robin
|
||||||
|
* failover
|
||||||
|
* @param info extra properties controlling the connection; notably, "password" if present
|
||||||
|
* supplies the password to authenticate with.
|
||||||
|
* @return the new, initialized, connection, or <code>null</code> if this protocol version is not
|
||||||
|
* supported by the server.
|
||||||
|
* @throws SQLException if the connection could not be established for a reason other than
|
||||||
|
* protocol version incompatibility.
|
||||||
|
*/
|
||||||
|
public abstract QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, Properties info) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely close the given stream.
|
||||||
|
*
|
||||||
|
* @param newStream The stream to close.
|
||||||
|
*/
|
||||||
|
protected void closeStream(PGStream newStream) {
|
||||||
|
if (newStream != null) {
|
||||||
|
try {
|
||||||
|
newStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Failed to closed stream with error: {0}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
348
pgjdbc/src/main/java/org/postgresql/core/Encoding.java
Normal file
348
pgjdbc/src/main/java/org/postgresql/core/Encoding.java
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a particular character encoding.
|
||||||
|
*/
|
||||||
|
public class Encoding {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(Encoding.class.getName());
|
||||||
|
|
||||||
|
private static final Encoding DEFAULT_ENCODING = new Encoding();
|
||||||
|
|
||||||
|
private static final Encoding UTF8_ENCODING = new Encoding(StandardCharsets.UTF_8, true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Preferred JVM encodings for backend encodings.
|
||||||
|
*/
|
||||||
|
private static final HashMap<String, String[]> encodings = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
//Note: this list should match the set of supported server
|
||||||
|
// encodings found in backend/util/mb/encnames.c
|
||||||
|
encodings.put("SQL_ASCII", new String[]{"ASCII", "US-ASCII"});
|
||||||
|
encodings.put("UNICODE", new String[]{"UTF-8", "UTF8"});
|
||||||
|
encodings.put("UTF8", new String[]{"UTF-8", "UTF8"});
|
||||||
|
encodings.put("LATIN1", new String[]{"ISO8859_1"});
|
||||||
|
encodings.put("LATIN2", new String[]{"ISO8859_2"});
|
||||||
|
encodings.put("LATIN3", new String[]{"ISO8859_3"});
|
||||||
|
encodings.put("LATIN4", new String[]{"ISO8859_4"});
|
||||||
|
encodings.put("ISO_8859_5", new String[]{"ISO8859_5"});
|
||||||
|
encodings.put("ISO_8859_6", new String[]{"ISO8859_6"});
|
||||||
|
encodings.put("ISO_8859_7", new String[]{"ISO8859_7"});
|
||||||
|
encodings.put("ISO_8859_8", new String[]{"ISO8859_8"});
|
||||||
|
encodings.put("LATIN5", new String[]{"ISO8859_9"});
|
||||||
|
encodings.put("LATIN7", new String[]{"ISO8859_13"});
|
||||||
|
encodings.put("LATIN9", new String[]{"ISO8859_15_FDIS"});
|
||||||
|
encodings.put("EUC_JP", new String[]{"EUC_JP"});
|
||||||
|
encodings.put("EUC_CN", new String[]{"EUC_CN"});
|
||||||
|
encodings.put("EUC_KR", new String[]{"EUC_KR"});
|
||||||
|
encodings.put("JOHAB", new String[]{"Johab"});
|
||||||
|
encodings.put("EUC_TW", new String[]{"EUC_TW"});
|
||||||
|
encodings.put("SJIS", new String[]{"MS932", "SJIS"});
|
||||||
|
encodings.put("BIG5", new String[]{"Big5", "MS950", "Cp950"});
|
||||||
|
encodings.put("GBK", new String[]{"GBK", "MS936"});
|
||||||
|
encodings.put("UHC", new String[]{"MS949", "Cp949", "Cp949C"});
|
||||||
|
encodings.put("TCVN", new String[]{"Cp1258"});
|
||||||
|
encodings.put("WIN1256", new String[]{"Cp1256"});
|
||||||
|
encodings.put("WIN1250", new String[]{"Cp1250"});
|
||||||
|
encodings.put("WIN874", new String[]{"MS874", "Cp874"});
|
||||||
|
encodings.put("WIN", new String[]{"Cp1251"});
|
||||||
|
encodings.put("ALT", new String[]{"Cp866"});
|
||||||
|
// We prefer KOI8-U, since it is a superset of KOI8-R.
|
||||||
|
encodings.put("KOI8", new String[]{"KOI8_U", "KOI8_R"});
|
||||||
|
// If the database isn't encoding-aware then we can't have
|
||||||
|
// any preferred encodings.
|
||||||
|
encodings.put("UNKNOWN", new String[0]);
|
||||||
|
// The following encodings do not have a java equivalent
|
||||||
|
encodings.put("MULE_INTERNAL", new String[0]);
|
||||||
|
encodings.put("LATIN6", new String[0]);
|
||||||
|
encodings.put("LATIN8", new String[0]);
|
||||||
|
encodings.put("LATIN10", new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final AsciiStringInterner INTERNER = new AsciiStringInterner();
|
||||||
|
|
||||||
|
private final Charset encoding;
|
||||||
|
private final boolean fastASCIINumbers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the default charset of the JVM.
|
||||||
|
*/
|
||||||
|
private Encoding() {
|
||||||
|
this(Charset.defaultCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses may use this constructor if they know in advance of their ASCII number
|
||||||
|
* compatibility.
|
||||||
|
*
|
||||||
|
* @param encoding charset to use
|
||||||
|
* @param fastASCIINumbers whether this encoding is compatible with ASCII numbers.
|
||||||
|
*/
|
||||||
|
protected Encoding(Charset encoding, boolean fastASCIINumbers) {
|
||||||
|
if (encoding == null) {
|
||||||
|
throw new NullPointerException("Null encoding charset not supported");
|
||||||
|
}
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.fastASCIINumbers = fastASCIINumbers;
|
||||||
|
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||||
|
LOGGER.log(Level.FINEST, "Creating new Encoding {0} with fastASCIINumbers {1}",
|
||||||
|
new Object[]{encoding, fastASCIINumbers});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the charset passed as parameter and tests at creation time whether the specified encoding
|
||||||
|
* is compatible with ASCII numbers.
|
||||||
|
*
|
||||||
|
* @param encoding charset to use
|
||||||
|
*/
|
||||||
|
protected Encoding(Charset encoding) {
|
||||||
|
this(encoding, testAsciiNumbers(encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this encoding has characters '-' and '0'..'9' in exactly same position as
|
||||||
|
* ascii.
|
||||||
|
*
|
||||||
|
* @return true if the bytes can be scanned directly for ascii numbers.
|
||||||
|
*/
|
||||||
|
public boolean hasAsciiNumbers() {
|
||||||
|
return fastASCIINumbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an Encoding for a given JVM encoding.
|
||||||
|
*
|
||||||
|
* @param jvmEncoding the name of the JVM encoding
|
||||||
|
* @return an Encoding instance for the specified encoding, or an Encoding instance for the
|
||||||
|
* default JVM encoding if the specified encoding is unavailable.
|
||||||
|
*/
|
||||||
|
public static Encoding getJVMEncoding(String jvmEncoding) {
|
||||||
|
if ("UTF-8".equals(jvmEncoding)) {
|
||||||
|
return UTF8_ENCODING;
|
||||||
|
}
|
||||||
|
if (Charset.isSupported(jvmEncoding)) {
|
||||||
|
return new Encoding(Charset.forName(jvmEncoding));
|
||||||
|
}
|
||||||
|
return DEFAULT_ENCODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an Encoding for a given database encoding.
|
||||||
|
*
|
||||||
|
* @param databaseEncoding the name of the database encoding
|
||||||
|
* @return an Encoding instance for the specified encoding, or an Encoding instance for the
|
||||||
|
* default JVM encoding if the specified encoding is unavailable.
|
||||||
|
*/
|
||||||
|
public static Encoding getDatabaseEncoding(String databaseEncoding) {
|
||||||
|
if ("UTF8".equals(databaseEncoding) || "UNICODE".equals(databaseEncoding)) {
|
||||||
|
return UTF8_ENCODING;
|
||||||
|
}
|
||||||
|
// If the backend encoding is known and there is a suitable
|
||||||
|
// encoding in the JVM we use that. Otherwise we fall back
|
||||||
|
// to the default encoding of the JVM.
|
||||||
|
String[] candidates = encodings.get(databaseEncoding);
|
||||||
|
if (candidates != null) {
|
||||||
|
for (String candidate : candidates) {
|
||||||
|
LOGGER.log(Level.FINEST, "Search encoding candidate {0}", candidate);
|
||||||
|
if (Charset.isSupported(candidate)) {
|
||||||
|
return new Encoding(Charset.forName(candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the encoding name directly -- maybe the charset has been
|
||||||
|
// provided by the user.
|
||||||
|
if (Charset.isSupported(databaseEncoding)) {
|
||||||
|
return new Encoding(Charset.forName(databaseEncoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to default JVM encoding.
|
||||||
|
LOGGER.log(Level.FINEST, "{0} encoding not found, returning default encoding", databaseEncoding);
|
||||||
|
return DEFAULT_ENCODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that <i>string</i> should be staged as a canonicalized value.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is intended for use with {@code String} constants.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param string The string to maintain canonicalized reference to. Must not be {@code null}.
|
||||||
|
* @see Encoding#decodeCanonicalized(byte[], int, int)
|
||||||
|
*/
|
||||||
|
public static void canonicalize(String string) {
|
||||||
|
INTERNER.putString(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the (JVM) encoding used.
|
||||||
|
*
|
||||||
|
* @return the JVM encoding name used by this instance.
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return encoding.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a string to an array of bytes.
|
||||||
|
*
|
||||||
|
* @param s the string to encode
|
||||||
|
* @return a bytearray containing the encoded string
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public byte [] encode(String s) throws IOException {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getBytes(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an array of bytes possibly into a canonicalized string.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Only ascii compatible encoding support canonicalization and only ascii {@code String} values are eligible
|
||||||
|
* to be canonicalized.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param encodedString a byte array containing the string to decode
|
||||||
|
* @param offset the offset in <code>encodedString</code> of the first byte of the encoded
|
||||||
|
* representation
|
||||||
|
* @param length the length, in bytes, of the encoded representation
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public String decodeCanonicalized(byte[] encodedString, int offset, int length) throws IOException {
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// if fastASCIINumbers is false, then no chance of the byte[] being ascii compatible characters
|
||||||
|
return fastASCIINumbers ? INTERNER.getString(encodedString, offset, length, this)
|
||||||
|
: decode(encodedString, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decodeCanonicalizedIfPresent(byte[] encodedString, int offset, int length) throws IOException {
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// if fastASCIINumbers is false, then no chance of the byte[] being ascii compatible characters
|
||||||
|
return fastASCIINumbers ? INTERNER.getStringIfPresent(encodedString, offset, length, this)
|
||||||
|
: decode(encodedString, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an array of bytes possibly into a canonicalized string.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Only ascii compatible encoding support canonicalization and only ascii {@code String} values are eligible
|
||||||
|
* to be canonicalized.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param encodedString a byte array containing the string to decode
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public String decodeCanonicalized(byte[] encodedString) throws IOException {
|
||||||
|
return decodeCanonicalized(encodedString, 0, encodedString.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an array of bytes into a string.
|
||||||
|
*
|
||||||
|
* @param encodedString a byte array containing the string to decode
|
||||||
|
* @param offset the offset in <code>encodedString</code> of the first byte of the encoded
|
||||||
|
* representation
|
||||||
|
* @param length the length, in bytes, of the encoded representation
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public String decode(byte[] encodedString, int offset, int length) throws IOException {
|
||||||
|
return new String(encodedString, offset, length, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an array of bytes into a string.
|
||||||
|
*
|
||||||
|
* @param encodedString a byte array containing the string to decode
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public String decode(byte[] encodedString) throws IOException {
|
||||||
|
return decode(encodedString, 0, encodedString.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Reader that decodes the given InputStream using this encoding.
|
||||||
|
*
|
||||||
|
* @param in the underlying stream to decode from
|
||||||
|
* @return a non-null Reader implementation.
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public Reader getDecodingReader(InputStream in) throws IOException {
|
||||||
|
return new InputStreamReader(in, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Writer that encodes to the given OutputStream using this encoding.
|
||||||
|
*
|
||||||
|
* @param out the underlying stream to encode to
|
||||||
|
* @return a non-null Writer implementation.
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public Writer getEncodingWriter(OutputStream out) throws IOException {
|
||||||
|
return new OutputStreamWriter(out, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an Encoding using the default encoding for the JVM.
|
||||||
|
*
|
||||||
|
* @return an Encoding instance
|
||||||
|
*/
|
||||||
|
public static Encoding defaultEncoding() {
|
||||||
|
return DEFAULT_ENCODING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return encoding.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this encoding is compatible with ASCII for the number characters '-' and
|
||||||
|
* '0'..'9'. Where compatible means that they are encoded with exactly same values.
|
||||||
|
*
|
||||||
|
* @return If faster ASCII number parsing can be used with this encoding.
|
||||||
|
*/
|
||||||
|
private static boolean testAsciiNumbers(Charset encoding) {
|
||||||
|
// TODO: test all postgres supported encoding to see if there are
|
||||||
|
// any which do _not_ have ascii numbers in same location
|
||||||
|
// at least all the encoding listed in the encodings hashmap have
|
||||||
|
// working ascii numbers
|
||||||
|
String test = "-0123456789";
|
||||||
|
byte[] bytes = test.getBytes(encoding);
|
||||||
|
String res = new String(bytes, StandardCharsets.US_ASCII);
|
||||||
|
return test.equals(res);
|
||||||
|
}
|
||||||
|
}
|
151
pgjdbc/src/main/java/org/postgresql/core/EncodingPredictor.java
Normal file
151
pgjdbc/src/main/java/org/postgresql/core/EncodingPredictor.java
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Predicts encoding for error messages based on some heuristics.</p>
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>For certain languages, it is known how "FATAL" is translated</li>
|
||||||
|
* <li>For Japanese, several common words are hardcoded</li>
|
||||||
|
* <li>Then try various LATIN encodings</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
public class EncodingPredictor {
|
||||||
|
|
||||||
|
public EncodingPredictor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In certain cases the encoding is not known for sure (e.g. before authentication).
|
||||||
|
* In such cases, backend might send messages in "native to database" encoding,
|
||||||
|
* thus pgjdbc has to guess the encoding nad
|
||||||
|
*/
|
||||||
|
public static class DecodeResult {
|
||||||
|
public final String result;
|
||||||
|
public final String encoding; // JVM name
|
||||||
|
|
||||||
|
DecodeResult(String result, String encoding) {
|
||||||
|
this.result = result;
|
||||||
|
this.encoding = encoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Translation {
|
||||||
|
public final String fatalText;
|
||||||
|
private final String [] texts;
|
||||||
|
public final String language;
|
||||||
|
public final String[] encodings;
|
||||||
|
|
||||||
|
Translation(String fatalText, String [] texts,
|
||||||
|
String language, String... encodings) {
|
||||||
|
this.fatalText = fatalText;
|
||||||
|
this.texts = texts;
|
||||||
|
this.language = language;
|
||||||
|
this.encodings = encodings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Translation[] FATAL_TRANSLATIONS =
|
||||||
|
new Translation[]{
|
||||||
|
new Translation("ВАЖНО", null, "ru", "WIN", "ALT", "KOI8"),
|
||||||
|
new Translation("致命错误", null, "zh_CN", "EUC_CN", "GBK", "BIG5"),
|
||||||
|
new Translation("KATASTROFALNY", null, "pl", "LATIN2"),
|
||||||
|
new Translation("FATALE", null, "it", "LATIN1", "LATIN9"),
|
||||||
|
new Translation("FATAL", new String[]{"は存在しません" /* ~ does not exist */,
|
||||||
|
"ロール" /* ~ role */, "ユーザ" /* ~ user */}, "ja", "EUC_JP", "SJIS"),
|
||||||
|
new Translation(null, null, "fr/de/es/pt_BR", "LATIN1", "LATIN3", "LATIN4", "LATIN5",
|
||||||
|
"LATIN7", "LATIN9"),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static DecodeResult decode(byte[] bytes, int offset, int length) {
|
||||||
|
Encoding defaultEncoding = Encoding.defaultEncoding();
|
||||||
|
for (Translation tr : FATAL_TRANSLATIONS) {
|
||||||
|
for (String encoding : tr.encodings) {
|
||||||
|
Encoding encoder = Encoding.getDatabaseEncoding(encoding);
|
||||||
|
if (encoder == defaultEncoding) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a translation for "FATAL", then try typical encodings for that language
|
||||||
|
if (tr.fatalText != null) {
|
||||||
|
byte[] encoded;
|
||||||
|
try {
|
||||||
|
byte[] tmp = encoder.encode(tr.fatalText);
|
||||||
|
encoded = new byte[tmp.length + 2];
|
||||||
|
encoded[0] = 'S';
|
||||||
|
encoded[encoded.length - 1] = 0;
|
||||||
|
System.arraycopy(tmp, 0, encoded, 1, tmp.length);
|
||||||
|
} catch (IOException e) {
|
||||||
|
continue;// should not happen
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!arrayContains(bytes, offset, length, encoded, 0, encoded.length)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea how to tell Japanese from Latin languages, thus just hard-code certain Japanese words
|
||||||
|
if (tr.texts != null) {
|
||||||
|
boolean foundOne = false;
|
||||||
|
for (String text : tr.texts) {
|
||||||
|
try {
|
||||||
|
byte[] textBytes = encoder.encode(text);
|
||||||
|
if (arrayContains(bytes, offset, length, textBytes, 0, textBytes.length)) {
|
||||||
|
foundOne = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// do not care, will try other encodings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundOne) {
|
||||||
|
// Error message does not have key parts, will try other encodings
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String decoded = encoder.decode(bytes, offset, length);
|
||||||
|
if (decoded.indexOf(65533) != -1) {
|
||||||
|
// bad character in string, try another encoding
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return new DecodeResult(decoded, encoder.name());
|
||||||
|
} catch (IOException e) {
|
||||||
|
// do not care
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean arrayContains(
|
||||||
|
byte[] first, int firstOffset, int firstLength,
|
||||||
|
byte[] second, int secondOffset, int secondLength
|
||||||
|
) {
|
||||||
|
if (firstLength < secondLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < firstLength; i++) {
|
||||||
|
for (; i < firstLength && first[firstOffset + i] != second[secondOffset]; i++) {
|
||||||
|
// find the first matching byte
|
||||||
|
}
|
||||||
|
|
||||||
|
int j = 1;
|
||||||
|
for (; j < secondLength && first[firstOffset + i + j] == second[secondOffset + j]; j++) {
|
||||||
|
// compare arrays
|
||||||
|
}
|
||||||
|
if (j == secondLength) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
176
pgjdbc/src/main/java/org/postgresql/core/Field.java
Normal file
176
pgjdbc/src/main/java/org/postgresql/core/Field.java
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.jdbc.FieldMetadata;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class Field {
|
||||||
|
// The V3 protocol defines two constants for the format of data
|
||||||
|
public static final int TEXT_FORMAT = 0;
|
||||||
|
public static final int BINARY_FORMAT = 1;
|
||||||
|
|
||||||
|
private final int length; // Internal Length of this field
|
||||||
|
private final int oid; // OID of the type
|
||||||
|
private final int mod; // type modifier of this field
|
||||||
|
private String columnLabel; // Column label
|
||||||
|
|
||||||
|
private int format = TEXT_FORMAT; // In the V3 protocol each field has a format
|
||||||
|
// 0 = text, 1 = binary
|
||||||
|
// In the V2 protocol all fields in a
|
||||||
|
// binary cursor are binary and all
|
||||||
|
// others are text
|
||||||
|
|
||||||
|
private final int tableOid; // OID of table ( zero if no table )
|
||||||
|
private final int positionInTable;
|
||||||
|
|
||||||
|
// Cache fields filled in by AbstractJdbc2ResultSetMetaData.fetchFieldMetaData.
|
||||||
|
// Don't use unless that has been called.
|
||||||
|
private FieldMetadata metadata;
|
||||||
|
|
||||||
|
private int sqlType;
|
||||||
|
private String pgType = NOT_YET_LOADED;
|
||||||
|
|
||||||
|
// New string to avoid clashes with other strings
|
||||||
|
private static final String NOT_YET_LOADED = new String("pgType is not yet loaded");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a field based on the information fed to it.
|
||||||
|
*
|
||||||
|
* @param name the name (column name and label) of the field
|
||||||
|
* @param oid the OID of the field
|
||||||
|
* @param length the length of the field
|
||||||
|
* @param mod modifier
|
||||||
|
*/
|
||||||
|
public Field(String name, int oid, int length, int mod) {
|
||||||
|
this(name, oid, length, mod, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor without mod parameter.
|
||||||
|
*
|
||||||
|
* @param name the name (column name and label) of the field
|
||||||
|
* @param oid the OID of the field
|
||||||
|
*/
|
||||||
|
public Field(String name, int oid) {
|
||||||
|
this(name, oid, 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a field based on the information fed to it.
|
||||||
|
* @param columnLabel the column label of the field
|
||||||
|
* @param oid the OID of the field
|
||||||
|
* @param length the length of the field
|
||||||
|
* @param mod modifier
|
||||||
|
* @param tableOid the OID of the columns' table
|
||||||
|
* @param positionInTable the position of column in the table (first column is 1, second column is 2, etc...)
|
||||||
|
*/
|
||||||
|
public Field(String columnLabel, int oid, int length, int mod, int tableOid,
|
||||||
|
int positionInTable) {
|
||||||
|
this.columnLabel = columnLabel;
|
||||||
|
this.oid = oid;
|
||||||
|
this.length = length;
|
||||||
|
this.mod = mod;
|
||||||
|
this.tableOid = tableOid;
|
||||||
|
this.positionInTable = positionInTable;
|
||||||
|
this.metadata = tableOid == 0 ? new FieldMetadata(columnLabel) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the oid of this Field's data type
|
||||||
|
*/
|
||||||
|
public int getOID() {
|
||||||
|
return oid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the mod of this Field's data type
|
||||||
|
*/
|
||||||
|
public int getMod() {
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the column label of this Field's data type
|
||||||
|
*/
|
||||||
|
public String getColumnLabel() {
|
||||||
|
return columnLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the length of this Field's data type
|
||||||
|
*/
|
||||||
|
public int getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the format of this Field's data (text=0, binary=1)
|
||||||
|
*/
|
||||||
|
public int getFormat() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param format the format of this Field's data (text=0, binary=1)
|
||||||
|
*/
|
||||||
|
public void setFormat(int format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the columns' table oid, zero if no oid available
|
||||||
|
*/
|
||||||
|
public int getTableOid() {
|
||||||
|
return tableOid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPositionInTable() {
|
||||||
|
return positionInTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(FieldMetadata metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Field(" + (columnLabel != null ? columnLabel : "")
|
||||||
|
+ "," + Oid.toString(oid)
|
||||||
|
+ "," + length
|
||||||
|
+ "," + (format == TEXT_FORMAT ? 'T' : 'B')
|
||||||
|
+ ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSQLType(int sqlType) {
|
||||||
|
this.sqlType = sqlType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSQLType() {
|
||||||
|
return sqlType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPGType(String pgType) {
|
||||||
|
this.pgType = pgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPGType() {
|
||||||
|
return pgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTypeInitialized() {
|
||||||
|
return pgType != NOT_YET_LOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void upperCaseLabel() {
|
||||||
|
columnLabel = columnLabel.toUpperCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stream that refuses to write more than a maximum number of bytes.
|
||||||
|
*/
|
||||||
|
public class FixedLengthOutputStream extends OutputStream {
|
||||||
|
|
||||||
|
private final int size;
|
||||||
|
private final OutputStream target;
|
||||||
|
private int written;
|
||||||
|
|
||||||
|
public FixedLengthOutputStream(int size, OutputStream target) {
|
||||||
|
this.size = size;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
verifyAllowed(1);
|
||||||
|
written++;
|
||||||
|
target.write(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buf, int offset, int len) throws IOException {
|
||||||
|
if ((offset < 0) || (len < 0) || ((offset + len) > buf.length)) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
} else if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
verifyAllowed(len);
|
||||||
|
target.write(buf, offset, len);
|
||||||
|
written += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int remaining() {
|
||||||
|
return size - written;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyAllowed(int wanted) throws IOException {
|
||||||
|
if (remaining() < wanted) {
|
||||||
|
throw new IOException("Attempt to write more than the specified " + size + " bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
pgjdbc/src/main/java/org/postgresql/core/JavaVersion.java
Normal file
37
pgjdbc/src/main/java/org/postgresql/core/JavaVersion.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
public enum JavaVersion {
|
||||||
|
// Note: order is important,
|
||||||
|
v1_8,
|
||||||
|
other;
|
||||||
|
|
||||||
|
private static final JavaVersion RUNTIME_VERSION = from(System.getProperty("java.version"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns enum value that represents current runtime. For instance, when using -jre7.jar via Java
|
||||||
|
* 8, this would return v18
|
||||||
|
*
|
||||||
|
* @return enum value that represents current runtime.
|
||||||
|
*/
|
||||||
|
public static JavaVersion getRuntimeVersion() {
|
||||||
|
return RUNTIME_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java version string like in {@code "java.version"} property.
|
||||||
|
*
|
||||||
|
* @param version string like 1.6, 1.7, etc
|
||||||
|
* @return JavaVersion enum
|
||||||
|
*/
|
||||||
|
public static JavaVersion from(String version) {
|
||||||
|
if (version.startsWith("1.8")) {
|
||||||
|
return v1_8;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains parse flags from {@link Parser#modifyJdbcCall(String, boolean, int, int, EscapeSyntaxCallMode)}.
|
||||||
|
*/
|
||||||
|
public class JdbcCallParseInfo {
|
||||||
|
private final String sql;
|
||||||
|
private final boolean isFunction;
|
||||||
|
|
||||||
|
public JdbcCallParseInfo(String sql, boolean isFunction) {
|
||||||
|
this.sql = sql;
|
||||||
|
this.isFunction = isFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL in a native for certain backend version.
|
||||||
|
*
|
||||||
|
* @return SQL in a native for certain backend version
|
||||||
|
*/
|
||||||
|
public String getSql() {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if given SQL is a function.
|
||||||
|
*
|
||||||
|
* @return {@code true} if given SQL is a function
|
||||||
|
*/
|
||||||
|
public boolean isFunction() {
|
||||||
|
return isFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
116
pgjdbc/src/main/java/org/postgresql/core/NativeQuery.java
Normal file
116
pgjdbc/src/main/java/org/postgresql/core/NativeQuery.java
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a query that is ready for execution by backend. The main difference from JDBC is ? are
|
||||||
|
* replaced with $1, $2, etc.
|
||||||
|
*/
|
||||||
|
public class NativeQuery {
|
||||||
|
private static final String[] BIND_NAMES = new String[128 * 10];
|
||||||
|
private static final int[] NO_BINDS = new int[0];
|
||||||
|
|
||||||
|
public final String nativeSql;
|
||||||
|
public final int[] bindPositions;
|
||||||
|
public final SqlCommand command;
|
||||||
|
public final boolean multiStatement;
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int i = 1; i < BIND_NAMES.length; i++) {
|
||||||
|
BIND_NAMES[i] = "$" + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQuery(String nativeSql, SqlCommand dml) {
|
||||||
|
this(nativeSql, NO_BINDS, true, dml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NativeQuery(String nativeSql, int [] bindPositions, boolean multiStatement, SqlCommand dml) {
|
||||||
|
this.nativeSql = nativeSql;
|
||||||
|
this.bindPositions =
|
||||||
|
bindPositions == null || bindPositions.length == 0 ? NO_BINDS : bindPositions;
|
||||||
|
this.multiStatement = multiStatement;
|
||||||
|
this.command = dml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stringize this query to a human-readable form, substituting particular parameter values for
|
||||||
|
* parameter placeholders.
|
||||||
|
*
|
||||||
|
* @param parameters a ParameterList returned by this Query's {@link Query#createParameterList}
|
||||||
|
* method, or {@code null} to leave the parameter placeholders unsubstituted.
|
||||||
|
* @return a human-readable representation of this query
|
||||||
|
*/
|
||||||
|
public String toString(ParameterList parameters) {
|
||||||
|
if (bindPositions.length == 0) {
|
||||||
|
return nativeSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
int queryLength = nativeSql.length();
|
||||||
|
String[] params = new String[bindPositions.length];
|
||||||
|
for (int i = 1; i <= bindPositions.length; i++) {
|
||||||
|
String param = parameters == null ? "?" : parameters.toString(i, true);
|
||||||
|
params[i - 1] = param;
|
||||||
|
queryLength += param.length() - bindName(i).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sbuf = new StringBuilder(queryLength);
|
||||||
|
sbuf.append(nativeSql, 0, bindPositions[0]);
|
||||||
|
for (int i = 1; i <= bindPositions.length; i++) {
|
||||||
|
sbuf.append(params[i - 1]);
|
||||||
|
int nextBind = i < bindPositions.length ? bindPositions[i] : nativeSql.length();
|
||||||
|
sbuf.append(nativeSql, bindPositions[i - 1] + bindName(i).length(), nextBind);
|
||||||
|
}
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns $1, $2, etc names of bind variables used by backend.
|
||||||
|
*
|
||||||
|
* @param index index of a bind variable
|
||||||
|
* @return bind variable name
|
||||||
|
*/
|
||||||
|
public static String bindName(int index) {
|
||||||
|
return index < BIND_NAMES.length ? BIND_NAMES[index] : "$" + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringBuilder appendBindName(StringBuilder sb, int index) {
|
||||||
|
if (index < BIND_NAMES.length) {
|
||||||
|
return sb.append(bindName(index));
|
||||||
|
}
|
||||||
|
sb.append('$');
|
||||||
|
sb.append(index);
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the text length required for the given number of bind variables
|
||||||
|
* including dollars.
|
||||||
|
* Do this to avoid repeated calls to
|
||||||
|
* AbstractStringBuilder.expandCapacity(...) and Arrays.copyOf
|
||||||
|
*
|
||||||
|
* @param bindCount total number of parameters in a query
|
||||||
|
* @return int total character length for $xyz kind of binds
|
||||||
|
*/
|
||||||
|
public static int calculateBindLength(int bindCount) {
|
||||||
|
int res = 0;
|
||||||
|
int bindLen = 2; // $1
|
||||||
|
int maxBindsOfLen = 9; // $0 .. $9
|
||||||
|
while (bindCount > 0) {
|
||||||
|
int numBinds = Math.min(maxBindsOfLen, bindCount);
|
||||||
|
bindCount -= numBinds;
|
||||||
|
res += bindLen * numBinds;
|
||||||
|
bindLen++;
|
||||||
|
maxBindsOfLen *= 10; // $0..$9 (9 items) -> $10..$99 (90 items)
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlCommand getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
}
|
47
pgjdbc/src/main/java/org/postgresql/core/Notification.java
Normal file
47
pgjdbc/src/main/java/org/postgresql/core/Notification.java
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGNotification;
|
||||||
|
|
||||||
|
public class Notification implements PGNotification {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String parameter;
|
||||||
|
private final int pid;
|
||||||
|
|
||||||
|
public Notification(String name, int pid) {
|
||||||
|
this(name, pid, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Notification(String name, int pid, String parameter) {
|
||||||
|
this.name = name;
|
||||||
|
this.pid = pid;
|
||||||
|
this.parameter = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns name of this notification
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the process id of the backend process making this notification
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getPID() {
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getParameter() {
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
148
pgjdbc/src/main/java/org/postgresql/core/Oid.java
Normal file
148
pgjdbc/src/main/java/org/postgresql/core/Oid.java
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides constants for well-known backend OIDs for the types we commonly use.
|
||||||
|
*/
|
||||||
|
public class Oid {
|
||||||
|
public static final int UNSPECIFIED = 0;
|
||||||
|
public static final int INT2 = 21;
|
||||||
|
public static final int INT2_ARRAY = 1005;
|
||||||
|
public static final int INT4 = 23;
|
||||||
|
public static final int INT4_ARRAY = 1007;
|
||||||
|
public static final int INT8 = 20;
|
||||||
|
public static final int INT8_ARRAY = 1016;
|
||||||
|
public static final int TEXT = 25;
|
||||||
|
public static final int TEXT_ARRAY = 1009;
|
||||||
|
public static final int NUMERIC = 1700;
|
||||||
|
public static final int NUMERIC_ARRAY = 1231;
|
||||||
|
public static final int FLOAT4 = 700;
|
||||||
|
public static final int FLOAT4_ARRAY = 1021;
|
||||||
|
public static final int FLOAT8 = 701;
|
||||||
|
public static final int FLOAT8_ARRAY = 1022;
|
||||||
|
public static final int BOOL = 16;
|
||||||
|
public static final int BOOL_ARRAY = 1000;
|
||||||
|
public static final int DATE = 1082;
|
||||||
|
public static final int DATE_ARRAY = 1182;
|
||||||
|
public static final int TIME = 1083;
|
||||||
|
public static final int TIME_ARRAY = 1183;
|
||||||
|
public static final int TIMETZ = 1266;
|
||||||
|
public static final int TIMETZ_ARRAY = 1270;
|
||||||
|
public static final int TIMESTAMP = 1114;
|
||||||
|
public static final int TIMESTAMP_ARRAY = 1115;
|
||||||
|
public static final int TIMESTAMPTZ = 1184;
|
||||||
|
public static final int TIMESTAMPTZ_ARRAY = 1185;
|
||||||
|
public static final int BYTEA = 17;
|
||||||
|
public static final int BYTEA_ARRAY = 1001;
|
||||||
|
public static final int VARCHAR = 1043;
|
||||||
|
public static final int VARCHAR_ARRAY = 1015;
|
||||||
|
public static final int OID = 26;
|
||||||
|
public static final int OID_ARRAY = 1028;
|
||||||
|
public static final int BPCHAR = 1042;
|
||||||
|
public static final int BPCHAR_ARRAY = 1014;
|
||||||
|
public static final int MONEY = 790;
|
||||||
|
public static final int MONEY_ARRAY = 791;
|
||||||
|
public static final int NAME = 19;
|
||||||
|
public static final int NAME_ARRAY = 1003;
|
||||||
|
public static final int BIT = 1560;
|
||||||
|
public static final int BIT_ARRAY = 1561;
|
||||||
|
public static final int VOID = 2278;
|
||||||
|
public static final int INTERVAL = 1186;
|
||||||
|
public static final int INTERVAL_ARRAY = 1187;
|
||||||
|
public static final int CHAR = 18; // This is not char(N), this is "char" a single byte type.
|
||||||
|
public static final int CHAR_ARRAY = 1002;
|
||||||
|
public static final int VARBIT = 1562;
|
||||||
|
public static final int VARBIT_ARRAY = 1563;
|
||||||
|
public static final int UUID = 2950;
|
||||||
|
public static final int UUID_ARRAY = 2951;
|
||||||
|
public static final int XML = 142;
|
||||||
|
public static final int XML_ARRAY = 143;
|
||||||
|
public static final int POINT = 600;
|
||||||
|
public static final int POINT_ARRAY = 1017;
|
||||||
|
public static final int BOX = 603;
|
||||||
|
public static final int BOX_ARRAY = 1020;
|
||||||
|
public static final int JSONB = 3802;
|
||||||
|
public static final int JSONB_ARRAY = 3807;
|
||||||
|
public static final int JSON = 114;
|
||||||
|
public static final int JSON_ARRAY = 199;
|
||||||
|
public static final int REF_CURSOR = 1790;
|
||||||
|
public static final int REF_CURSOR_ARRAY = 2201;
|
||||||
|
public static final int LINE = 628;
|
||||||
|
public static final int LSEG = 601;
|
||||||
|
public static final int PATH = 602;
|
||||||
|
public static final int POLYGON = 604;
|
||||||
|
public static final int CIRCLE = 718;
|
||||||
|
public static final int CIDR = 650;
|
||||||
|
public static final int INET = 869;
|
||||||
|
public static final int MACADDR = 829;
|
||||||
|
public static final int MACADDR8 = 774;
|
||||||
|
public static final int TSVECTOR = 3614;
|
||||||
|
public static final int TSQUERY = 3615;
|
||||||
|
|
||||||
|
private static final Map<Integer, String> OID_TO_NAME = new HashMap<>(100);
|
||||||
|
private static final Map<String, Integer> NAME_TO_OID = new HashMap<>(100);
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (Field field : Oid.class.getFields()) {
|
||||||
|
try {
|
||||||
|
int oid = field.getInt(null);
|
||||||
|
String name = field.getName().toUpperCase(Locale.ROOT);
|
||||||
|
OID_TO_NAME.put(oid, name);
|
||||||
|
NAME_TO_OID.put(name, oid);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Oid() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the oid as string.
|
||||||
|
*
|
||||||
|
* @param oid The oid to convert to name.
|
||||||
|
* @return The name of the oid or {@code "<unknown>"} if oid no constant for oid value has been
|
||||||
|
* defined.
|
||||||
|
*/
|
||||||
|
public static String toString(int oid) {
|
||||||
|
String name = OID_TO_NAME.get(oid);
|
||||||
|
if (name == null) {
|
||||||
|
name = "<unknown:" + oid + ">";
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int valueOf(String oid) throws PSQLException {
|
||||||
|
if (oid.length() > 0 && !Character.isDigit(oid.charAt(0))) {
|
||||||
|
Integer id = NAME_TO_OID.get(oid);
|
||||||
|
if (id == null) {
|
||||||
|
id = NAME_TO_OID.get(oid.toUpperCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// OID are unsigned 32bit integers, so Integer.parseInt is not enough
|
||||||
|
return (int) Long.parseLong(oid);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new PSQLException(GT.tr("oid type {0} not known and not a number", oid),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class PGBindException extends IOException {
|
||||||
|
|
||||||
|
private final IOException ioe;
|
||||||
|
|
||||||
|
public PGBindException(IOException ioe) {
|
||||||
|
this.ioe = ioe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOException getIOException() {
|
||||||
|
return ioe;
|
||||||
|
}
|
||||||
|
}
|
846
pgjdbc/src/main/java/org/postgresql/core/PGStream.java
Normal file
846
pgjdbc/src/main/java/org/postgresql/core/PGStream.java
Normal file
|
@ -0,0 +1,846 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2017, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.gss.GSSInputStream;
|
||||||
|
import org.postgresql.gss.GSSOutputStream;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
import org.postgresql.util.PGPropertyMaxResultBufferParser;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import org.ietf.jgss.GSSContext;
|
||||||
|
import org.ietf.jgss.MessageProp;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.Flushable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Wrapper around the raw connection to the server that implements some basic primitives
|
||||||
|
* (reading/writing formatted data, doing string encoding, etc).</p>
|
||||||
|
*
|
||||||
|
* <p>In general, instances of PGStream are not threadsafe; the caller must ensure that only one thread
|
||||||
|
* at a time is accessing a particular PGStream instance.</p>
|
||||||
|
*/
|
||||||
|
public class PGStream implements Closeable, Flushable {
|
||||||
|
private final SocketFactory socketFactory;
|
||||||
|
private final HostSpec hostSpec;
|
||||||
|
|
||||||
|
private final byte[] int4Buf;
|
||||||
|
private final byte[] int2Buf;
|
||||||
|
|
||||||
|
private Socket connection;
|
||||||
|
private VisibleBufferedInputStream pgInput;
|
||||||
|
private OutputStream pgOutput;
|
||||||
|
private byte [] streamBuffer;
|
||||||
|
|
||||||
|
public boolean isGssEncrypted() {
|
||||||
|
return gssEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean gssEncrypted;
|
||||||
|
|
||||||
|
public void setSecContext(GSSContext secContext) {
|
||||||
|
MessageProp messageProp = new MessageProp(0, true);
|
||||||
|
pgInput = new VisibleBufferedInputStream(new GSSInputStream(pgInput.getWrapped(), secContext, messageProp ), 8192);
|
||||||
|
pgOutput = new GSSOutputStream(pgOutput, secContext, messageProp, 16384);
|
||||||
|
gssEncrypted = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private long nextStreamAvailableCheckTime;
|
||||||
|
// This is a workaround for SSL sockets: sslInputStream.available() might return 0
|
||||||
|
// so we perform "1ms reads" once in a while
|
||||||
|
private int minStreamAvailableCheckDelay = 1000;
|
||||||
|
|
||||||
|
private Encoding encoding;
|
||||||
|
private Writer encodingWriter;
|
||||||
|
|
||||||
|
private long maxResultBuffer = -1;
|
||||||
|
private long resultBufferByteCount;
|
||||||
|
|
||||||
|
private int maxRowSizeBytes = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor: Connect to the PostgreSQL back end and return a stream connection.
|
||||||
|
*
|
||||||
|
* @param socketFactory socket factory to use when creating sockets
|
||||||
|
* @param hostSpec the host and port to connect to
|
||||||
|
* @param timeout timeout in milliseconds, or 0 if no timeout set
|
||||||
|
* @throws IOException if an IOException occurs below it.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public PGStream(SocketFactory socketFactory, HostSpec hostSpec, int timeout) throws IOException {
|
||||||
|
this.socketFactory = socketFactory;
|
||||||
|
this.hostSpec = hostSpec;
|
||||||
|
|
||||||
|
Socket socket = createSocket(timeout);
|
||||||
|
changeSocket(socket);
|
||||||
|
setEncoding(Encoding.getJVMEncoding("UTF-8"));
|
||||||
|
|
||||||
|
int2Buf = new byte[2];
|
||||||
|
int4Buf = new byte[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
public PGStream(PGStream pgStream, int timeout) throws IOException {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Some defaults
|
||||||
|
*/
|
||||||
|
int sendBufferSize = 1024;
|
||||||
|
int receiveBufferSize = 1024;
|
||||||
|
int soTimeout = 0;
|
||||||
|
boolean keepAlive = false;
|
||||||
|
boolean tcpNoDelay = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the existing values before closing the stream
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
sendBufferSize = pgStream.getSocket().getSendBufferSize();
|
||||||
|
receiveBufferSize = pgStream.getSocket().getReceiveBufferSize();
|
||||||
|
soTimeout = pgStream.getSocket().getSoTimeout();
|
||||||
|
keepAlive = pgStream.getSocket().getKeepAlive();
|
||||||
|
tcpNoDelay = pgStream.getSocket().getTcpNoDelay();
|
||||||
|
|
||||||
|
} catch ( SocketException ex ) {
|
||||||
|
// ignore it
|
||||||
|
}
|
||||||
|
//close the existing stream
|
||||||
|
pgStream.close();
|
||||||
|
|
||||||
|
this.socketFactory = pgStream.socketFactory;
|
||||||
|
this.hostSpec = pgStream.hostSpec;
|
||||||
|
|
||||||
|
Socket socket = createSocket(timeout);
|
||||||
|
changeSocket(socket);
|
||||||
|
setEncoding(Encoding.getJVMEncoding("UTF-8"));
|
||||||
|
// set the buffer sizes and timeout
|
||||||
|
socket.setReceiveBufferSize(receiveBufferSize);
|
||||||
|
socket.setSendBufferSize(sendBufferSize);
|
||||||
|
setNetworkTimeout(soTimeout);
|
||||||
|
socket.setKeepAlive(keepAlive);
|
||||||
|
socket.setTcpNoDelay(tcpNoDelay);
|
||||||
|
|
||||||
|
int2Buf = new byte[2];
|
||||||
|
int4Buf = new byte[4];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor: Connect to the PostgreSQL back end and return a stream connection.
|
||||||
|
*
|
||||||
|
* @param socketFactory socket factory
|
||||||
|
* @param hostSpec the host and port to connect to
|
||||||
|
* @throws IOException if an IOException occurs below it.
|
||||||
|
* @deprecated use {@link #PGStream(SocketFactory, org.postgresql.util.HostSpec, int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public PGStream(SocketFactory socketFactory, HostSpec hostSpec) throws IOException {
|
||||||
|
this(socketFactory, hostSpec, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HostSpec getHostSpec() {
|
||||||
|
return hostSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketFactory getSocketFactory() {
|
||||||
|
return socketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for pending backend messages without blocking. Might return false when there actually are
|
||||||
|
* messages waiting, depending on the characteristics of the underlying socket. This is used to
|
||||||
|
* detect asynchronous notifies from the backend, when available.
|
||||||
|
*
|
||||||
|
* @return true if there is a pending backend message
|
||||||
|
* @throws IOException if something wrong happens
|
||||||
|
*/
|
||||||
|
public boolean hasMessagePending() throws IOException {
|
||||||
|
|
||||||
|
boolean available = false;
|
||||||
|
|
||||||
|
// In certain cases, available returns 0, yet there are bytes
|
||||||
|
if (pgInput.available() > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
long now = System.nanoTime() / 1000000;
|
||||||
|
|
||||||
|
if (now < nextStreamAvailableCheckTime && minStreamAvailableCheckDelay != 0) {
|
||||||
|
// Do not use ".peek" too often
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int soTimeout = getNetworkTimeout();
|
||||||
|
connection.setSoTimeout(1);
|
||||||
|
try {
|
||||||
|
if (!pgInput.ensureBytes(1, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
available = pgInput.peek() != -1;
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
connection.setSoTimeout(soTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If none available then set the next check time
|
||||||
|
In the event that there more async bytes available we will continue to get them all
|
||||||
|
see issue 1547 https://github.com/pgjdbc/pgjdbc/issues/1547
|
||||||
|
*/
|
||||||
|
if (!available) {
|
||||||
|
nextStreamAvailableCheckTime = now + minStreamAvailableCheckDelay;
|
||||||
|
}
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinStreamAvailableCheckDelay(int delay) {
|
||||||
|
this.minStreamAvailableCheckDelay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Socket createSocket(int timeout) throws IOException {
|
||||||
|
Socket socket = null;
|
||||||
|
try {
|
||||||
|
socket = socketFactory.createSocket();
|
||||||
|
String localSocketAddress = hostSpec.getLocalSocketAddress();
|
||||||
|
if (localSocketAddress != null) {
|
||||||
|
socket.bind(new InetSocketAddress(InetAddress.getByName(localSocketAddress), 0));
|
||||||
|
}
|
||||||
|
if (!socket.isConnected()) {
|
||||||
|
// When using a SOCKS proxy, the host might not be resolvable locally,
|
||||||
|
// thus we defer resolution until the traffic reaches the proxy. If there
|
||||||
|
// is no proxy, we must resolve the host to an IP to connect the socket.
|
||||||
|
InetSocketAddress address = hostSpec.shouldResolve()
|
||||||
|
? new InetSocketAddress(hostSpec.getHost(), hostSpec.getPort())
|
||||||
|
: InetSocketAddress.createUnresolved(hostSpec.getHost(), hostSpec.getPort());
|
||||||
|
socket.connect(address, timeout);
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
} catch ( Exception ex ) {
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch ( Exception ex1 ) {
|
||||||
|
ex.addSuppressed(ex1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch this stream to using a new socket. Any existing socket is <em>not</em> closed; it's
|
||||||
|
* assumed that we are changing to a new socket that delegates to the original socket (e.g. SSL).
|
||||||
|
*
|
||||||
|
* @param socket the new socket to change to
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public void changeSocket(Socket socket) throws IOException {
|
||||||
|
assert connection != socket : "changeSocket is called with the current socket as argument."
|
||||||
|
+ " This is a no-op, however, it re-allocates buffered streams, so refrain from"
|
||||||
|
+ " excessive changeSocket calls";
|
||||||
|
|
||||||
|
this.connection = socket;
|
||||||
|
|
||||||
|
// Submitted by Jason Venner <jason@idiom.com>. Disable Nagle
|
||||||
|
// as we are selective about flushing output only when we
|
||||||
|
// really need to.
|
||||||
|
connection.setTcpNoDelay(true);
|
||||||
|
|
||||||
|
// Buffer sizes submitted by Sverre H Huseby <sverrehu@online.no>
|
||||||
|
pgInput = new VisibleBufferedInputStream(connection.getInputStream(), 8192);
|
||||||
|
pgOutput = new BufferedOutputStream(connection.getOutputStream(), 8192);
|
||||||
|
|
||||||
|
if (encoding != null) {
|
||||||
|
setEncoding(encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Encoding getEncoding() {
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the encoding used by this connection.
|
||||||
|
*
|
||||||
|
* @param encoding the new encoding to use
|
||||||
|
* @throws IOException if something goes wrong
|
||||||
|
*/
|
||||||
|
public void setEncoding(Encoding encoding) throws IOException {
|
||||||
|
if (this.encoding != null && this.encoding.name().equals(encoding.name())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Close down any old writer.
|
||||||
|
if (encodingWriter != null) {
|
||||||
|
encodingWriter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.encoding = encoding;
|
||||||
|
|
||||||
|
// Intercept flush() downcalls from the writer; our caller
|
||||||
|
// will call PGStream.flush() as needed.
|
||||||
|
OutputStream interceptor = new FilterOutputStream(pgOutput) {
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
encodingWriter = encoding.getEncodingWriter(interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get a Writer instance that encodes directly onto the underlying stream.</p>
|
||||||
|
*
|
||||||
|
* <p>The returned Writer should not be closed, as it's a shared object. Writer.flush needs to be
|
||||||
|
* called when switching between use of the Writer and use of the PGStream write methods, but it
|
||||||
|
* won't actually flush output all the way out -- call {@link #flush} to actually ensure all
|
||||||
|
* output has been pushed to the server.</p>
|
||||||
|
*
|
||||||
|
* @return the shared Writer instance
|
||||||
|
* @throws IOException if something goes wrong.
|
||||||
|
*/
|
||||||
|
public Writer getEncodingWriter() throws IOException {
|
||||||
|
if (encodingWriter == null) {
|
||||||
|
throw new IOException("No encoding has been set on this connection");
|
||||||
|
}
|
||||||
|
return encodingWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a single character to the back end.
|
||||||
|
*
|
||||||
|
* @param val the character to be sent
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void sendChar(int val) throws IOException {
|
||||||
|
pgOutput.write(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a 4-byte integer to the back end.
|
||||||
|
*
|
||||||
|
* @param val the integer to be sent
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void sendInteger4(int val) throws IOException {
|
||||||
|
int4Buf[0] = (byte) (val >>> 24);
|
||||||
|
int4Buf[1] = (byte) (val >>> 16);
|
||||||
|
int4Buf[2] = (byte) (val >>> 8);
|
||||||
|
int4Buf[3] = (byte) (val);
|
||||||
|
pgOutput.write(int4Buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a 2-byte integer (short) to the back end.
|
||||||
|
*
|
||||||
|
* @param val the integer to be sent
|
||||||
|
* @throws IOException if an I/O error occurs or {@code val} cannot be encoded in 2 bytes
|
||||||
|
*/
|
||||||
|
public void sendInteger2(int val) throws IOException {
|
||||||
|
if (val < 0 || val > 65535) {
|
||||||
|
throw new IllegalArgumentException("Tried to send an out-of-range integer as a 2-byte unsigned int value: " + val);
|
||||||
|
}
|
||||||
|
int2Buf[0] = (byte) (val >>> 8);
|
||||||
|
int2Buf[1] = (byte) val;
|
||||||
|
pgOutput.write(int2Buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an array of bytes to the backend.
|
||||||
|
*
|
||||||
|
* @param buf The array of bytes to be sent
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void send(byte[] buf) throws IOException {
|
||||||
|
pgOutput.write(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a fixed-size array of bytes to the backend. If {@code buf.length < siz}, pad with zeros.
|
||||||
|
* If {@code buf.length > siz}, truncate the array.
|
||||||
|
*
|
||||||
|
* @param buf the array of bytes to be sent
|
||||||
|
* @param siz the number of bytes to be sent
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void send(byte[] buf, int siz) throws IOException {
|
||||||
|
send(buf, 0, siz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a fixed-size array of bytes to the backend. If {@code length < siz}, pad with zeros. If
|
||||||
|
* {@code length > siz}, truncate the array.
|
||||||
|
*
|
||||||
|
* @param buf the array of bytes to be sent
|
||||||
|
* @param off offset in the array to start sending from
|
||||||
|
* @param siz the number of bytes to be sent
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void send(byte[] buf, int off, int siz) throws IOException {
|
||||||
|
int bufamt = buf.length - off;
|
||||||
|
pgOutput.write(buf, off, bufamt < siz ? bufamt : siz);
|
||||||
|
for (int i = bufamt; i < siz; i++) {
|
||||||
|
pgOutput.write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a fixed-size array of bytes to the backend. If {@code length < siz}, pad with zeros. If
|
||||||
|
* {@code length > siz}, truncate the array.
|
||||||
|
*
|
||||||
|
* @param writer the stream writer to invoke to send the bytes
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void send(ByteStreamWriter writer) throws IOException {
|
||||||
|
final FixedLengthOutputStream fixedLengthStream = new FixedLengthOutputStream(writer.getLength(), pgOutput);
|
||||||
|
try {
|
||||||
|
writer.writeTo(new ByteStreamWriter.ByteStreamTarget() {
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() {
|
||||||
|
return fixedLengthStream;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw ioe;
|
||||||
|
} catch (Exception re) {
|
||||||
|
throw new IOException("Error writing bytes to stream", re);
|
||||||
|
}
|
||||||
|
for (int i = fixedLengthStream.remaining(); i > 0; i--) {
|
||||||
|
pgOutput.write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a single character from the backend, without advancing the current protocol stream
|
||||||
|
* position.
|
||||||
|
*
|
||||||
|
* @return the character received
|
||||||
|
* @throws IOException if an I/O Error occurs
|
||||||
|
*/
|
||||||
|
public int peekChar() throws IOException {
|
||||||
|
int c = pgInput.peek();
|
||||||
|
if (c < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a single character from the backend.
|
||||||
|
*
|
||||||
|
* @return the character received
|
||||||
|
* @throws IOException if an I/O Error occurs
|
||||||
|
*/
|
||||||
|
public int receiveChar() throws IOException {
|
||||||
|
int c = pgInput.read();
|
||||||
|
if (c < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a four byte integer from the backend.
|
||||||
|
*
|
||||||
|
* @return the integer received from the backend
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public int receiveInteger4() throws IOException {
|
||||||
|
if (pgInput.read(int4Buf) != 4) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int4Buf[0] & 0xFF) << 24 | (int4Buf[1] & 0xFF) << 16 | (int4Buf[2] & 0xFF) << 8
|
||||||
|
| int4Buf[3] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a two byte integer from the backend.
|
||||||
|
*
|
||||||
|
* @return the integer received from the backend
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public int receiveInteger2() throws IOException {
|
||||||
|
if (pgInput.read(int2Buf) != 2) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int2Buf[0] & 0xFF) << 8 | int2Buf[1] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a fixed-size string from the backend.
|
||||||
|
*
|
||||||
|
* @param len the length of the string to receive, in bytes.
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something wrong happens
|
||||||
|
*/
|
||||||
|
public String receiveString(int len) throws IOException {
|
||||||
|
if (!pgInput.ensureBytes(len)) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String res = encoding.decode(pgInput.getBuffer(), pgInput.getIndex(), len);
|
||||||
|
pgInput.skip(len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a fixed-size string from the backend, and tries to avoid "UTF-8 decode failed"
|
||||||
|
* errors.
|
||||||
|
*
|
||||||
|
* @param len the length of the string to receive, in bytes.
|
||||||
|
* @return the decoded string
|
||||||
|
* @throws IOException if something wrong happens
|
||||||
|
*/
|
||||||
|
public EncodingPredictor.DecodeResult receiveErrorString(int len) throws IOException {
|
||||||
|
if (!pgInput.ensureBytes(len)) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
|
||||||
|
EncodingPredictor.DecodeResult res;
|
||||||
|
try {
|
||||||
|
String value = encoding.decode(pgInput.getBuffer(), pgInput.getIndex(), len);
|
||||||
|
// no autodetect warning as the message was converted on its own
|
||||||
|
res = new EncodingPredictor.DecodeResult(value, null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
res = EncodingPredictor.decode(pgInput.getBuffer(), pgInput.getIndex(), len);
|
||||||
|
if (res == null) {
|
||||||
|
Encoding enc = Encoding.defaultEncoding();
|
||||||
|
String value = enc.decode(pgInput.getBuffer(), pgInput.getIndex(), len);
|
||||||
|
res = new EncodingPredictor.DecodeResult(value, enc.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pgInput.skip(len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a null-terminated string from the backend. If we don't see a null, then we assume
|
||||||
|
* something has gone wrong.
|
||||||
|
*
|
||||||
|
* @return string from back end
|
||||||
|
* @throws IOException if an I/O error occurs, or end of file
|
||||||
|
*/
|
||||||
|
public String receiveString() throws IOException {
|
||||||
|
int len = pgInput.scanCStringLength();
|
||||||
|
String res = encoding.decode(pgInput.getBuffer(), pgInput.getIndex(), len - 1);
|
||||||
|
pgInput.skip(len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a null-terminated string from the backend and attempts to decode to a
|
||||||
|
* {@link Encoding#decodeCanonicalized(byte[], int, int) canonical} {@code String}.
|
||||||
|
* If we don't see a null, then we assume something has gone wrong.
|
||||||
|
*
|
||||||
|
* @return string from back end
|
||||||
|
* @throws IOException if an I/O error occurs, or end of file
|
||||||
|
* @see Encoding#decodeCanonicalized(byte[], int, int)
|
||||||
|
*/
|
||||||
|
public String receiveCanonicalString() throws IOException {
|
||||||
|
int len = pgInput.scanCStringLength();
|
||||||
|
String res = encoding.decodeCanonicalized(pgInput.getBuffer(), pgInput.getIndex(), len - 1);
|
||||||
|
pgInput.skip(len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a null-terminated string from the backend and attempts to decode to a
|
||||||
|
* {@link Encoding#decodeCanonicalizedIfPresent(byte[], int, int) canonical} {@code String}.
|
||||||
|
* If we don't see a null, then we assume something has gone wrong.
|
||||||
|
*
|
||||||
|
* @return string from back end
|
||||||
|
* @throws IOException if an I/O error occurs, or end of file
|
||||||
|
* @see Encoding#decodeCanonicalizedIfPresent(byte[], int, int)
|
||||||
|
*/
|
||||||
|
public String receiveCanonicalStringIfPresent() throws IOException {
|
||||||
|
int len = pgInput.scanCStringLength();
|
||||||
|
String res = encoding.decodeCanonicalizedIfPresent(pgInput.getBuffer(), pgInput.getIndex(), len - 1);
|
||||||
|
pgInput.skip(len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a tuple from the back end. A tuple is a two dimensional array of bytes. This variant reads
|
||||||
|
* the V3 protocol's tuple representation.
|
||||||
|
*
|
||||||
|
* @return tuple from the back end
|
||||||
|
* @throws IOException if a data I/O error occurs
|
||||||
|
* @throws SQLException if read more bytes than set maxResultBuffer
|
||||||
|
*/
|
||||||
|
public Tuple receiveTupleV3() throws IOException, OutOfMemoryError, SQLException {
|
||||||
|
int messageSize = receiveInteger4(); // MESSAGE SIZE
|
||||||
|
int nf = receiveInteger2();
|
||||||
|
//size = messageSize - 4 bytes of message size - 2 bytes of field count - 4 bytes for each column length
|
||||||
|
int dataToReadSize = messageSize - 4 - 2 - 4 * nf;
|
||||||
|
setMaxRowSizeBytes(dataToReadSize);
|
||||||
|
|
||||||
|
byte[][] answer = new byte[nf][];
|
||||||
|
|
||||||
|
increaseByteCounter(dataToReadSize);
|
||||||
|
OutOfMemoryError oom = null;
|
||||||
|
for (int i = 0; i < nf; i++) {
|
||||||
|
int size = receiveInteger4();
|
||||||
|
if (size != -1) {
|
||||||
|
try {
|
||||||
|
answer[i] = new byte[size];
|
||||||
|
receive(answer[i], 0, size);
|
||||||
|
} catch (OutOfMemoryError oome) {
|
||||||
|
oom = oome;
|
||||||
|
skip(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oom != null) {
|
||||||
|
throw oom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tuple(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads in a given number of bytes from the backend.
|
||||||
|
*
|
||||||
|
* @param siz number of bytes to read
|
||||||
|
* @return array of bytes received
|
||||||
|
* @throws IOException if a data I/O error occurs
|
||||||
|
*/
|
||||||
|
public byte[] receive(int siz) throws IOException {
|
||||||
|
byte[] answer = new byte[siz];
|
||||||
|
receive(answer, 0, siz);
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads in a given number of bytes from the backend.
|
||||||
|
*
|
||||||
|
* @param buf buffer to store result
|
||||||
|
* @param off offset in buffer
|
||||||
|
* @param siz number of bytes to read
|
||||||
|
* @throws IOException if a data I/O error occurs
|
||||||
|
*/
|
||||||
|
public void receive(byte[] buf, int off, int siz) throws IOException {
|
||||||
|
int s = 0;
|
||||||
|
|
||||||
|
while (s < siz) {
|
||||||
|
int w = pgInput.read(buf, off + s, siz - s);
|
||||||
|
if (w < 0) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
s += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skip(int size) throws IOException {
|
||||||
|
long s = 0;
|
||||||
|
while (s < size) {
|
||||||
|
s += pgInput.skip(size - s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy data from an input stream to the connection.
|
||||||
|
*
|
||||||
|
* @param inStream the stream to read data from
|
||||||
|
* @param remaining the number of bytes to copy
|
||||||
|
* @throws IOException if a data I/O error occurs
|
||||||
|
*/
|
||||||
|
public void sendStream(InputStream inStream, int remaining) throws IOException {
|
||||||
|
int expectedLength = remaining;
|
||||||
|
byte[] streamBuffer = this.streamBuffer;
|
||||||
|
if (streamBuffer == null) {
|
||||||
|
this.streamBuffer = streamBuffer = new byte[8192];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
int count = remaining > streamBuffer.length ? streamBuffer.length : remaining;
|
||||||
|
int readCount;
|
||||||
|
|
||||||
|
try {
|
||||||
|
readCount = inStream.read(streamBuffer, 0, count);
|
||||||
|
if (readCount < 0) {
|
||||||
|
throw new EOFException(
|
||||||
|
GT.tr("Premature end of input stream, expected {0} bytes, but only read {1}.",
|
||||||
|
expectedLength, expectedLength - remaining));
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
while (remaining > 0) {
|
||||||
|
send(streamBuffer, count);
|
||||||
|
remaining -= count;
|
||||||
|
count = remaining > streamBuffer.length ? streamBuffer.length : remaining;
|
||||||
|
}
|
||||||
|
throw new PGBindException(ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(streamBuffer, readCount);
|
||||||
|
remaining -= readCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush any pending output to the backend.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
if (encodingWriter != null) {
|
||||||
|
encodingWriter.flush();
|
||||||
|
}
|
||||||
|
pgOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume an expected EOF from the backend.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
* @throws SQLException if we get something other than an EOF
|
||||||
|
*/
|
||||||
|
public void receiveEOF() throws SQLException, IOException {
|
||||||
|
int c = pgInput.read();
|
||||||
|
if (c < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new PSQLException(GT.tr("Expected an EOF from server, got: {0}", c),
|
||||||
|
PSQLState.COMMUNICATION_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connection.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O Error occurs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (encodingWriter != null) {
|
||||||
|
encodingWriter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pgOutput.close();
|
||||||
|
pgInput.close();
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNetworkTimeout(int milliseconds) throws IOException {
|
||||||
|
connection.setSoTimeout(milliseconds);
|
||||||
|
pgInput.setTimeoutRequested(milliseconds != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNetworkTimeout() throws IOException {
|
||||||
|
return connection.getSoTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set MaxResultBuffer inside PGStream.
|
||||||
|
*
|
||||||
|
* @param value value of new max result buffer as string (cause we can expect % or chars to use
|
||||||
|
* multiplier)
|
||||||
|
* @throws PSQLException exception returned when occurred parsing problem.
|
||||||
|
*/
|
||||||
|
public void setMaxResultBuffer(String value) throws PSQLException {
|
||||||
|
maxResultBuffer = PGPropertyMaxResultBufferParser.parseProperty(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get MaxResultBuffer from PGStream.
|
||||||
|
*
|
||||||
|
* @return size of MaxResultBuffer
|
||||||
|
*/
|
||||||
|
public long getMaxResultBuffer() {
|
||||||
|
return maxResultBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The idea behind this method is to keep in maxRowSize the size of biggest read data row. As
|
||||||
|
* there may be many data rows send after each other for a query, then value in maxRowSize would
|
||||||
|
* contain value noticed so far, because next data rows and their sizes are not read for that
|
||||||
|
* moment. We want it increasing, because the size of the biggest among data rows will be used
|
||||||
|
* during computing new adaptive fetch size for the query.
|
||||||
|
*
|
||||||
|
* @param rowSizeBytes new value to be set as maxRowSizeBytes
|
||||||
|
*/
|
||||||
|
public void setMaxRowSizeBytes(int rowSizeBytes) {
|
||||||
|
if (rowSizeBytes > maxRowSizeBytes) {
|
||||||
|
maxRowSizeBytes = rowSizeBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get actual max row size noticed so far.
|
||||||
|
*
|
||||||
|
* @return value of max row size
|
||||||
|
*/
|
||||||
|
public int getMaxRowSizeBytes() {
|
||||||
|
return maxRowSizeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear value of max row size noticed so far.
|
||||||
|
*/
|
||||||
|
public void clearMaxRowSizeBytes() {
|
||||||
|
maxRowSizeBytes = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear count of byte buffer.
|
||||||
|
*/
|
||||||
|
public void clearResultBufferCount() {
|
||||||
|
resultBufferByteCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increase actual count of buffer. If buffer count is bigger than max result buffer limit, then
|
||||||
|
* gonna return an exception.
|
||||||
|
*
|
||||||
|
* @param value size of bytes to add to byte buffer.
|
||||||
|
* @throws SQLException exception returned when result buffer count is bigger than max result
|
||||||
|
* buffer.
|
||||||
|
*/
|
||||||
|
private void increaseByteCounter(long value) throws SQLException {
|
||||||
|
if (maxResultBuffer != -1) {
|
||||||
|
resultBufferByteCount += value;
|
||||||
|
if (resultBufferByteCount > maxResultBuffer) {
|
||||||
|
throw new PSQLException(GT.tr(
|
||||||
|
"Result set exceeded maxResultBuffer limit. Received: {0}; Current limit: {1}",
|
||||||
|
String.valueOf(resultBufferByteCount), String.valueOf(maxResultBuffer)), PSQLState.COMMUNICATION_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return connection.isClosed();
|
||||||
|
}
|
||||||
|
}
|
208
pgjdbc/src/main/java/org/postgresql/core/ParameterList.java
Normal file
208
pgjdbc/src/main/java/org/postgresql/core/ParameterList.java
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Abstraction of a list of parameters to be substituted into a Query. The protocol-specific details
|
||||||
|
* of how to efficiently store and stream the parameters is hidden behind implementations of this
|
||||||
|
* interface.</p>
|
||||||
|
*
|
||||||
|
* <p>In general, instances of ParameterList are associated with a particular Query object (the one
|
||||||
|
* that created them) and shouldn't be used against another Query.</p>
|
||||||
|
*
|
||||||
|
* <p>Parameter indexes are 1-based to match JDBC's PreparedStatement, i.e. the first parameter has
|
||||||
|
* index 1.</p>
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public interface ParameterList {
|
||||||
|
void registerOutParameter(int index, int sqlType) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of parameters in this list. This value never changes for a particular instance,
|
||||||
|
* and might be zero.
|
||||||
|
*
|
||||||
|
* @return the number of parameters in this list.
|
||||||
|
*/
|
||||||
|
int getParameterCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of IN parameters in this list.
|
||||||
|
*
|
||||||
|
* @return the number of IN parameters in this list
|
||||||
|
*/
|
||||||
|
int getInParameterCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of OUT parameters in this list.
|
||||||
|
*
|
||||||
|
* @return the number of OUT parameters in this list
|
||||||
|
*/
|
||||||
|
int getOutParameterCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the oids of the parameters in this list. May be null for a ParameterList that does not
|
||||||
|
* support typing of parameters.
|
||||||
|
*
|
||||||
|
* @return oids of the parameters
|
||||||
|
*/
|
||||||
|
int[] getTypeOIDs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds an integer value to a parameter. The type of the parameter is implicitly 'int4'.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param value the integer value to use.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setIntParameter(int index, int value) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a String value that is an unquoted literal to the server's query parser (for example, a
|
||||||
|
* bare integer) to a parameter. Associated with the parameter is a typename for the parameter
|
||||||
|
* that should correspond to an entry in pg_types.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param value the unquoted literal string to use.
|
||||||
|
* @param oid the type OID of the parameter, or <code>0</code> to infer the type.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setLiteralParameter(int index,
|
||||||
|
String value, int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a String value that needs to be quoted for the server's parser to understand (for
|
||||||
|
* example, a timestamp) to a parameter. Associated with the parameter is a typename for the
|
||||||
|
* parameter that should correspond to an entry in pg_types.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param value the quoted string to use.
|
||||||
|
* @param oid the type OID of the parameter, or <code>0</code> to infer the type.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setStringParameter(int index, String value, int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a binary bytea value stored as a bytearray to a parameter. The parameter's type is
|
||||||
|
* implicitly set to 'bytea'. The bytearray's contains should remain unchanged until query
|
||||||
|
* execution has completed.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param data an array containing the raw data value
|
||||||
|
* @param offset the offset within <code>data</code> of the start of the parameter data.
|
||||||
|
* @param length the number of bytes of parameter data within <code>data</code> to use.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setBytea(int index, byte[] data,
|
||||||
|
int offset, int length) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a binary bytea value stored as an InputStream. The parameter's type is implicitly set to
|
||||||
|
* 'bytea'. The stream should remain valid until query execution has completed.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param stream a stream containing the parameter data.
|
||||||
|
* @param length the number of bytes of parameter data to read from <code>stream</code>.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setBytea(int index, InputStream stream, int length) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a binary bytea value stored as an InputStream. The parameter's type is implicitly set to
|
||||||
|
* 'bytea'. The stream should remain valid until query execution has completed.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param stream a stream containing the parameter data.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setBytea(int index, InputStream stream) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a binary bytea value stored as a ByteStreamWriter. The parameter's type is implicitly set to
|
||||||
|
* 'bytea'. The stream should remain valid until query execution has completed.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param writer a writer that can write the bytes for the parameter
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setBytea(int index, ByteStreamWriter writer) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a text value stored as an InputStream that is a valid UTF-8 byte stream.
|
||||||
|
* Any byte-order marks (BOM) in the stream are passed to the backend.
|
||||||
|
* The parameter's type is implicitly set to 'text'.
|
||||||
|
* The stream should remain valid until query execution has completed.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param stream a stream containing the parameter data.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setText(int index, InputStream stream) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds given byte[] value to a parameter. The bytes must already be in correct format matching
|
||||||
|
* the OID.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param value the bytes to send.
|
||||||
|
* @param oid the type OID of the parameter.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setBinaryParameter(int index, byte[] value, int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a SQL NULL value to a parameter. Associated with the parameter is a typename for the
|
||||||
|
* parameter that should correspond to an entry in pg_types.
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param oid the type OID of the parameter, or <code>0</code> to infer the type.
|
||||||
|
* @throws SQLException on error or if <code>index</code> is out of range
|
||||||
|
*/
|
||||||
|
void setNull(int index, int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a shallow copy of this ParameterList, returning a new instance (still suitable for
|
||||||
|
* passing to the owning Query). If this ParameterList is immutable, copy() may return the same
|
||||||
|
* immutable object.
|
||||||
|
*
|
||||||
|
* @return a new ParameterList instance
|
||||||
|
*/
|
||||||
|
ParameterList copy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbind all parameter values bound in this list.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a human-readable representation of a particular parameter in this ParameterList. If the
|
||||||
|
* parameter is not bound, returns "?".
|
||||||
|
*
|
||||||
|
* @param index the 1-based parameter index to bind.
|
||||||
|
* @param standardConformingStrings true if \ is not an escape character in strings literals
|
||||||
|
* @return a string representation of the parameter.
|
||||||
|
*/
|
||||||
|
String toString(int index, boolean standardConformingStrings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this operation to append more parameters to the current list.
|
||||||
|
* @param list of parameters to append with.
|
||||||
|
* @throws SQLException fault raised if driver or back end throw an exception
|
||||||
|
*/
|
||||||
|
void appendAll(ParameterList list) throws SQLException ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bound parameter values.
|
||||||
|
* @return Object array containing the parameter values.
|
||||||
|
*/
|
||||||
|
Object [] getValues();
|
||||||
|
}
|
1581
pgjdbc/src/main/java/org/postgresql/core/Parser.java
Normal file
1581
pgjdbc/src/main/java/org/postgresql/core/Parser.java
Normal file
File diff suppressed because it is too large
Load diff
21
pgjdbc/src/main/java/org/postgresql/core/Provider.java
Normal file
21
pgjdbc/src/main/java/org/postgresql/core/Provider.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a provider of results.
|
||||||
|
*
|
||||||
|
* @param <T> the type of results provided by this provider
|
||||||
|
*/
|
||||||
|
public interface Provider<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a result.
|
||||||
|
*
|
||||||
|
* @return a result
|
||||||
|
*/
|
||||||
|
T get();
|
||||||
|
}
|
87
pgjdbc/src/main/java/org/postgresql/core/Query.java
Normal file
87
pgjdbc/src/main/java/org/postgresql/core/Query.java
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Abstraction of a generic Query, hiding the details of any protocol-version-specific data needed
|
||||||
|
* to execute the query efficiently.</p>
|
||||||
|
*
|
||||||
|
* <p>Query objects should be explicitly closed when no longer needed; if resources are allocated on
|
||||||
|
* the server for this query, their cleanup is triggered by closing the Query.</p>
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public interface Query {
|
||||||
|
/**
|
||||||
|
* <p>Create a ParameterList suitable for storing parameters associated with this Query.</p>
|
||||||
|
*
|
||||||
|
* <p>If this query has no parameters, a ParameterList will be returned, but it may be a shared
|
||||||
|
* immutable object. If this query does have parameters, the returned ParameterList is a new list,
|
||||||
|
* unshared by other callers.</p>
|
||||||
|
*
|
||||||
|
* @return a suitable ParameterList instance for this query
|
||||||
|
*/
|
||||||
|
ParameterList createParameterList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stringize this query to a human-readable form, substituting particular parameter values for
|
||||||
|
* parameter placeholders.
|
||||||
|
*
|
||||||
|
* @param parameters a ParameterList returned by this Query's {@link #createParameterList} method,
|
||||||
|
* or <code>null</code> to leave the parameter placeholders unsubstituted.
|
||||||
|
* @return a human-readable representation of this query
|
||||||
|
*/
|
||||||
|
String toString(ParameterList parameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns SQL in native for database format.
|
||||||
|
* @return SQL in native for database format
|
||||||
|
*/
|
||||||
|
String getNativeSql();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns properties of the query (sql keyword, and some other parsing info).
|
||||||
|
* @return returns properties of the query (sql keyword, and some other parsing info) or null if not applicable
|
||||||
|
*/
|
||||||
|
SqlCommand getSqlCommand();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Close this query and free any server-side resources associated with it. The resources may not
|
||||||
|
* be immediately deallocated, but closing a Query may make the deallocation more prompt.</p>
|
||||||
|
*
|
||||||
|
* <p>A closed Query should not be executed.</p>
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
boolean isStatementDescribed();
|
||||||
|
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of times this Query has been batched.
|
||||||
|
* @return number of times <code>addBatch()</code> has been called.
|
||||||
|
*/
|
||||||
|
int getBatchSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map that a result set can use to find the index associated to a name.
|
||||||
|
*
|
||||||
|
* @return null if the query implementation does not support this method.
|
||||||
|
*/
|
||||||
|
Map<String, Integer> getResultSetColumnNameIndexMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of the Query objects that make up this query. If this object is already a
|
||||||
|
* SimpleQuery, returns null (avoids an extra array construction in the common case).
|
||||||
|
*
|
||||||
|
* @return an array of single-statement queries, or <code>null</code> if this object is already a
|
||||||
|
* single-statement query.
|
||||||
|
*/
|
||||||
|
Query [] getSubqueries();
|
||||||
|
}
|
623
pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
Normal file
623
pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
Normal file
|
@ -0,0 +1,623 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGNotification;
|
||||||
|
import org.postgresql.copy.CopyOperation;
|
||||||
|
import org.postgresql.core.v3.TypeTransferModeRegistry;
|
||||||
|
import org.postgresql.jdbc.AutoSave;
|
||||||
|
import org.postgresql.jdbc.BatchResultHandler;
|
||||||
|
import org.postgresql.jdbc.EscapeSyntaxCallMode;
|
||||||
|
import org.postgresql.jdbc.PreferQueryMode;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Abstracts the protocol-specific details of executing a query.</p>
|
||||||
|
*
|
||||||
|
* <p>Every connection has a single QueryExecutor implementation associated with it. This object
|
||||||
|
* provides:</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>factory methods for Query objects ({@link #createSimpleQuery(String)} and
|
||||||
|
* {@link #createQuery(String, boolean, boolean, String...)})
|
||||||
|
* <li>execution methods for created Query objects (
|
||||||
|
* {@link #execute(Query, ParameterList, ResultHandler, int, int, int)} for single queries and
|
||||||
|
* {@link #execute(Query[], ParameterList[], BatchResultHandler, int, int, int)} for batches of queries)
|
||||||
|
* <li>a fastpath call interface ({@link #createFastpathParameters} and {@link #fastpathCall}).
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Query objects may represent a query that has parameter placeholders. To provide actual values for
|
||||||
|
* these parameters, a {@link ParameterList} object is created via a factory method (
|
||||||
|
* {@link Query#createParameterList}). The parameters are filled in by the caller and passed along
|
||||||
|
* with the query to the query execution methods. Several ParameterLists for a given query might
|
||||||
|
* exist at one time (or over time); this allows the underlying Query to be reused for several
|
||||||
|
* executions, or for batch execution of the same Query.</p>
|
||||||
|
*
|
||||||
|
* <p>In general, a Query created by a particular QueryExecutor may only be executed by that
|
||||||
|
* QueryExecutor, and a ParameterList created by a particular Query may only be used as parameters
|
||||||
|
* to that Query. Unpredictable things will happen if this isn't done.</p>
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public interface QueryExecutor extends TypeTransferModeRegistry {
|
||||||
|
/**
|
||||||
|
* Flag for query execution that indicates the given Query object is unlikely to be reused.
|
||||||
|
*/
|
||||||
|
int QUERY_ONESHOT = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution that indicates that resultset metadata isn't needed and can be safely
|
||||||
|
* omitted.
|
||||||
|
*/
|
||||||
|
int QUERY_NO_METADATA = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution that indicates that a resultset isn't expected and the query executor
|
||||||
|
* can safely discard any rows (although the resultset should still appear to be from a
|
||||||
|
* resultset-returning query).
|
||||||
|
*/
|
||||||
|
int QUERY_NO_RESULTS = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution that indicates a forward-fetch-capable cursor should be used if
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
|
int QUERY_FORWARD_CURSOR = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution that indicates the automatic BEGIN on the first statement when outside
|
||||||
|
* a transaction should not be done.
|
||||||
|
*/
|
||||||
|
int QUERY_SUPPRESS_BEGIN = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution when we don't really want to execute, we just want to get the
|
||||||
|
* parameter metadata for the statement.
|
||||||
|
*/
|
||||||
|
int QUERY_DESCRIBE_ONLY = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution used by generated keys where we want to receive both the ResultSet and
|
||||||
|
* associated update count from the command status.
|
||||||
|
*/
|
||||||
|
int QUERY_BOTH_ROWS_AND_STATUS = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force this query to be described at each execution. This is done in pipelined batches where we
|
||||||
|
* might need to detect mismatched result types.
|
||||||
|
*/
|
||||||
|
int QUERY_FORCE_DESCRIBE_PORTAL = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to disable batch execution when we expect results (generated keys) from a statement.
|
||||||
|
*
|
||||||
|
* @deprecated in PgJDBC 9.4 as we now auto-size batches.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
int QUERY_DISALLOW_BATCHING = 128;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag for query execution to avoid using binary transfer.
|
||||||
|
*/
|
||||||
|
int QUERY_NO_BINARY_TRANSFER = 256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the query via simple 'Q' command (not parse, bind, exec, but simple execute).
|
||||||
|
* This sends query text on each execution, however it supports sending multiple queries
|
||||||
|
* separated with ';' as a single command.
|
||||||
|
*/
|
||||||
|
int QUERY_EXECUTE_AS_SIMPLE = 1024;
|
||||||
|
|
||||||
|
int MAX_SAVE_POINTS = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that when beginning a transaction, it should be read only.
|
||||||
|
*/
|
||||||
|
int QUERY_READ_ONLY_HINT = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a Query, passing results to a provided ResultHandler.
|
||||||
|
*
|
||||||
|
* @param query the query to execute; must be a query returned from calling
|
||||||
|
* {@link #wrap(List)} on this QueryExecutor object.
|
||||||
|
* @param parameters the parameters for the query. Must be non-<code>null</code> if the query
|
||||||
|
* takes parameters. Must be a parameter object returned by
|
||||||
|
* {@link org.postgresql.core.Query#createParameterList()}.
|
||||||
|
* @param handler a ResultHandler responsible for handling results generated by this query
|
||||||
|
* @param maxRows the maximum number of rows to retrieve
|
||||||
|
* @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
|
||||||
|
* before suspending
|
||||||
|
* @param flags a combination of QUERY_* flags indicating how to handle the query.
|
||||||
|
* @throws SQLException if query execution fails
|
||||||
|
*/
|
||||||
|
void execute(Query query, ParameterList parameters, ResultHandler handler, int maxRows,
|
||||||
|
int fetchSize, int flags) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a Query with adaptive fetch, passing results to a provided ResultHandler.
|
||||||
|
*
|
||||||
|
* @param query the query to execute; must be a query returned from calling
|
||||||
|
* {@link #wrap(List)} on this QueryExecutor object.
|
||||||
|
* @param parameters the parameters for the query. Must be non-<code>null</code> if the query
|
||||||
|
* takes parameters. Must be a parameter object returned by
|
||||||
|
* {@link org.postgresql.core.Query#createParameterList()}.
|
||||||
|
* @param handler a ResultHandler responsible for handling results generated by this query
|
||||||
|
* @param maxRows the maximum number of rows to retrieve
|
||||||
|
* @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
|
||||||
|
* before suspending
|
||||||
|
* @param flags a combination of QUERY_* flags indicating how to handle the query.
|
||||||
|
* @param adaptiveFetch state of adaptiveFetch to use during execution
|
||||||
|
* @throws SQLException if query execution fails
|
||||||
|
*/
|
||||||
|
void execute(Query query, ParameterList parameters, ResultHandler handler, int maxRows,
|
||||||
|
int fetchSize, int flags, boolean adaptiveFetch) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute several Query, passing results to a provided ResultHandler.
|
||||||
|
*
|
||||||
|
* @param queries the queries to execute; each must be a query returned from calling
|
||||||
|
* {@link #wrap(List)} on this QueryExecutor object.
|
||||||
|
* @param parameterLists the parameter lists for the queries. The parameter lists correspond 1:1
|
||||||
|
* to the queries passed in the <code>queries</code> array. Each must be non-
|
||||||
|
* <code>null</code> if the corresponding query takes parameters, and must be a parameter
|
||||||
|
* object returned by {@link Query#createParameterList()} created by
|
||||||
|
* the corresponding query.
|
||||||
|
* @param handler a ResultHandler responsible for handling results generated by this query
|
||||||
|
* @param maxRows the maximum number of rows to retrieve
|
||||||
|
* @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
|
||||||
|
* before suspending
|
||||||
|
* @param flags a combination of QUERY_* flags indicating how to handle the query.
|
||||||
|
* @throws SQLException if query execution fails
|
||||||
|
*/
|
||||||
|
void execute(Query[] queries, ParameterList[] parameterLists,
|
||||||
|
BatchResultHandler handler, int maxRows,
|
||||||
|
int fetchSize, int flags) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute several Query with adaptive fetch, passing results to a provided ResultHandler.
|
||||||
|
*
|
||||||
|
* @param queries the queries to execute; each must be a query returned from calling
|
||||||
|
* {@link #wrap(List)} on this QueryExecutor object.
|
||||||
|
* @param parameterLists the parameter lists for the queries. The parameter lists correspond 1:1
|
||||||
|
* to the queries passed in the <code>queries</code> array. Each must be non-
|
||||||
|
* <code>null</code> if the corresponding query takes parameters, and must be a parameter
|
||||||
|
* object returned by {@link Query#createParameterList()} created by
|
||||||
|
* the corresponding query.
|
||||||
|
* @param handler a ResultHandler responsible for handling results generated by this query
|
||||||
|
* @param maxRows the maximum number of rows to retrieve
|
||||||
|
* @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
|
||||||
|
* before suspending
|
||||||
|
* @param flags a combination of QUERY_* flags indicating how to handle the query.
|
||||||
|
* @param adaptiveFetch state of adaptiveFetch to use during execution
|
||||||
|
* @throws SQLException if query execution fails
|
||||||
|
*/
|
||||||
|
void execute(Query[] queries, ParameterList[] parameterLists,
|
||||||
|
BatchResultHandler handler, int maxRows,
|
||||||
|
int fetchSize, int flags, boolean adaptiveFetch) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch additional rows from a cursor.
|
||||||
|
*
|
||||||
|
* @param cursor the cursor to fetch from
|
||||||
|
* @param handler the handler to feed results to
|
||||||
|
* @param fetchSize the preferred number of rows to retrieve before suspending
|
||||||
|
* @param adaptiveFetch state of adaptiveFetch to use during fetching
|
||||||
|
* @throws SQLException if query execution fails
|
||||||
|
*/
|
||||||
|
void fetch(ResultCursor cursor, ResultHandler handler, int fetchSize, boolean adaptiveFetch) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an unparameterized Query object suitable for execution by this QueryExecutor. The
|
||||||
|
* provided query string is not parsed for parameter placeholders ('?' characters), and the
|
||||||
|
* {@link Query#createParameterList} of the returned object will always return an empty
|
||||||
|
* ParameterList.
|
||||||
|
*
|
||||||
|
* @param sql the SQL for the query to create
|
||||||
|
* @return a new Query object
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
Query createSimpleQuery(String sql) throws SQLException;
|
||||||
|
|
||||||
|
boolean isReWriteBatchedInsertsEnabled();
|
||||||
|
|
||||||
|
CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParameterized,
|
||||||
|
String ... columnNames)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
Object createQueryKey(String sql, boolean escapeProcessing, boolean isParameterized,
|
||||||
|
String ... columnNames);
|
||||||
|
|
||||||
|
CachedQuery createQueryByKey(Object key) throws SQLException;
|
||||||
|
|
||||||
|
CachedQuery borrowQueryByKey(Object key) throws SQLException;
|
||||||
|
|
||||||
|
CachedQuery borrowQuery(String sql) throws SQLException;
|
||||||
|
|
||||||
|
CachedQuery borrowCallableQuery(String sql) throws SQLException;
|
||||||
|
|
||||||
|
CachedQuery borrowReturningQuery(String sql, String [] columnNames) throws SQLException;
|
||||||
|
|
||||||
|
void releaseQuery(CachedQuery cachedQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap given native query into a ready for execution format.
|
||||||
|
* @param queries list of queries in native to database syntax
|
||||||
|
* @return query object ready for execution by this query executor
|
||||||
|
*/
|
||||||
|
Query wrap(List<NativeQuery> queries);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prior to attempting to retrieve notifications, we need to pull any recently received
|
||||||
|
* notifications off of the network buffers. The notification retrieval in ProtocolConnection
|
||||||
|
* cannot do this as it is prone to deadlock, so the higher level caller must be responsible which
|
||||||
|
* requires exposing this method.
|
||||||
|
*
|
||||||
|
* @throws SQLException if and error occurs while fetching notifications
|
||||||
|
*/
|
||||||
|
void processNotifies() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prior to attempting to retrieve notifications, we need to pull any recently received
|
||||||
|
* notifications off of the network buffers. The notification retrieval in ProtocolConnection
|
||||||
|
* cannot do this as it is prone to deadlock, so the higher level caller must be responsible which
|
||||||
|
* requires exposing this method. This variant supports blocking for the given time in millis.
|
||||||
|
*
|
||||||
|
* @param timeoutMillis number of milliseconds to block for
|
||||||
|
* @throws SQLException if and error occurs while fetching notifications
|
||||||
|
*/
|
||||||
|
void processNotifies(int timeoutMillis) throws SQLException;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Fastpath interface.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ParameterList implementation suitable for invoking a fastpath function via
|
||||||
|
* {@link #fastpathCall}.
|
||||||
|
*
|
||||||
|
* @param count the number of parameters the fastpath call will take
|
||||||
|
* @return a ParameterList suitable for passing to {@link #fastpathCall}.
|
||||||
|
* @deprecated This API is somewhat obsolete, as one may achieve similar performance
|
||||||
|
* and greater functionality by setting up a prepared statement to define
|
||||||
|
* the function call. Then, executing the statement with binary transmission of parameters
|
||||||
|
* and results substitutes for a fast-path function call.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
ParameterList createFastpathParameters(int count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke a backend function via the fastpath interface.
|
||||||
|
*
|
||||||
|
* @param fnid the OID of the backend function to invoke
|
||||||
|
* @param params a ParameterList returned from {@link #createFastpathParameters} containing the
|
||||||
|
* parameters to pass to the backend function
|
||||||
|
* @param suppressBegin if begin should be suppressed
|
||||||
|
* @return the binary-format result of the fastpath call, or <code>null</code> if a void result
|
||||||
|
* was returned
|
||||||
|
* @throws SQLException if an error occurs while executing the fastpath call
|
||||||
|
* @deprecated This API is somewhat obsolete, as one may achieve similar performance
|
||||||
|
* and greater functionality by setting up a prepared statement to define
|
||||||
|
* the function call. Then, executing the statement with binary transmission of parameters
|
||||||
|
* and results substitutes for a fast-path function call.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
byte [] fastpathCall(int fnid, ParameterList params, boolean suppressBegin)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues a COPY FROM STDIN / COPY TO STDOUT statement and returns handler for associated
|
||||||
|
* operation. Until the copy operation completes, no other database operation may be performed.
|
||||||
|
* Implemented for protocol version 3 only.
|
||||||
|
*
|
||||||
|
* @param sql input sql
|
||||||
|
* @param suppressBegin if begin should be suppressed
|
||||||
|
* @return handler for associated operation
|
||||||
|
* @throws SQLException when initializing the given query fails
|
||||||
|
*/
|
||||||
|
CopyOperation startCopy(String sql, boolean suppressBegin) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the version of the implementation
|
||||||
|
*/
|
||||||
|
int getProtocolVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single oid that should be received using binary encoding.
|
||||||
|
*
|
||||||
|
* @param oid The oid to request with binary encoding.
|
||||||
|
*/
|
||||||
|
void addBinaryReceiveOid(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove given oid from the list of oids for binary receive encoding.
|
||||||
|
* <p>Note: the binary receive for the oid can be re-activated later.</p>
|
||||||
|
*
|
||||||
|
* @param oid The oid to request with binary encoding.
|
||||||
|
*/
|
||||||
|
void removeBinaryReceiveOid(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the oids that should be received using binary encoding.
|
||||||
|
* <p>Note: this returns an unmodifiable set, and its contents might not reflect the current state.</p>
|
||||||
|
*
|
||||||
|
* @return The oids to request with binary encoding.
|
||||||
|
* @deprecated the method returns a copy of the set, so it is not efficient. Use {@link #useBinaryForReceive(int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Set<? extends Integer> getBinaryReceiveOids();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the oids that should be received using binary encoding.
|
||||||
|
*
|
||||||
|
* @param useBinaryForOids The oids to request with binary encoding.
|
||||||
|
*/
|
||||||
|
void setBinaryReceiveOids(Set<Integer> useBinaryForOids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single oid that should be sent using binary encoding.
|
||||||
|
*
|
||||||
|
* @param oid The oid to send with binary encoding.
|
||||||
|
*/
|
||||||
|
void addBinarySendOid(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove given oid from the list of oids for binary send encoding.
|
||||||
|
* <p>Note: the binary send for the oid can be re-activated later.</p>
|
||||||
|
*
|
||||||
|
* @param oid The oid to send with binary encoding.
|
||||||
|
*/
|
||||||
|
void removeBinarySendOid(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the oids that should be sent using binary encoding.
|
||||||
|
* <p>Note: this returns an unmodifiable set, and its contents might not reflect the current state.</p>
|
||||||
|
*
|
||||||
|
* @return useBinaryForOids The oids to send with binary encoding.
|
||||||
|
* @deprecated the method returns a copy of the set, so it is not efficient. Use {@link #useBinaryForSend(int)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Set<? extends Integer> getBinarySendOids();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the oids that should be sent using binary encoding.
|
||||||
|
*
|
||||||
|
* @param useBinaryForOids The oids to send with binary encoding.
|
||||||
|
*/
|
||||||
|
void setBinarySendOids(Set<Integer> useBinaryForOids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if server uses integer instead of double for binary date and time encodings.
|
||||||
|
*
|
||||||
|
* @return the server integer_datetime setting.
|
||||||
|
*/
|
||||||
|
boolean getIntegerDateTimes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the host and port this connection is connected to.
|
||||||
|
*/
|
||||||
|
HostSpec getHostSpec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the user this connection authenticated as.
|
||||||
|
*/
|
||||||
|
String getUser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the database this connection is connected to.
|
||||||
|
*/
|
||||||
|
String getDatabase();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a query cancellation for this connection.
|
||||||
|
*
|
||||||
|
* @throws SQLException if something goes wrong.
|
||||||
|
*/
|
||||||
|
void sendQueryCancel() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the process ID (PID) of the backend server process handling this connection.
|
||||||
|
*
|
||||||
|
* @return process ID (PID) of the backend server process handling this connection
|
||||||
|
*/
|
||||||
|
int getBackendPID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort at network level without sending the Terminate message to the backend.
|
||||||
|
*/
|
||||||
|
void abort();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close this connection cleanly.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an action that would close the connection cleanly.
|
||||||
|
* The returned object should refer only the minimum subset of objects required
|
||||||
|
* for proper resource cleanup. For instance, it should better not hold a strong reference to
|
||||||
|
* {@link QueryExecutor}.
|
||||||
|
* @return action that would close the connection cleanly.
|
||||||
|
*/
|
||||||
|
Closeable getCloseAction();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this connection is closed.
|
||||||
|
*
|
||||||
|
* @return true iff the connection is closed.
|
||||||
|
*/
|
||||||
|
boolean isClosed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Return the server version from the server_version GUC.</p>
|
||||||
|
*
|
||||||
|
* <p>Note that there's no requirement for this to be numeric or of the form x.y.z. PostgreSQL
|
||||||
|
* development releases usually have the format x.ydevel e.g. 9.4devel; betas usually x.ybetan
|
||||||
|
* e.g. 9.4beta1. The --with-extra-version configure option may add an arbitrary string to this.</p>
|
||||||
|
*
|
||||||
|
* <p>Don't use this string for logic, only use it when displaying the server version to the user.
|
||||||
|
* Prefer getServerVersionNum() for all logic purposes.</p>
|
||||||
|
*
|
||||||
|
* @return the server version string from the server_version GUC
|
||||||
|
*/
|
||||||
|
String getServerVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve and clear the set of asynchronous notifications pending on this connection.
|
||||||
|
*
|
||||||
|
* @return an array of notifications; if there are no notifications, an empty array is returned.
|
||||||
|
* @throws SQLException if and error occurs while fetching notifications
|
||||||
|
*/
|
||||||
|
PGNotification[] getNotifications() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve and clear the chain of warnings accumulated on this connection.
|
||||||
|
*
|
||||||
|
* @return the first SQLWarning in the chain; subsequent warnings can be found via
|
||||||
|
* SQLWarning.getNextWarning().
|
||||||
|
*/
|
||||||
|
SQLWarning getWarnings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get a machine-readable server version.</p>
|
||||||
|
*
|
||||||
|
* <p>This returns the value of the server_version_num GUC. If no such GUC exists, it falls back on
|
||||||
|
* attempting to parse the text server version for the major version. If there's no minor version
|
||||||
|
* (e.g. a devel or beta release) then the minor version is set to zero. If the version could not
|
||||||
|
* be parsed, zero is returned.</p>
|
||||||
|
*
|
||||||
|
* @return the server version in numeric XXYYZZ form, eg 090401, from server_version_num
|
||||||
|
*/
|
||||||
|
int getServerVersionNum();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current transaction state of this connection.
|
||||||
|
*
|
||||||
|
* @return a ProtocolConnection.TRANSACTION_* constant.
|
||||||
|
*/
|
||||||
|
TransactionState getTransactionState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the server treats string-literals according to the SQL standard or if it uses
|
||||||
|
* traditional PostgreSQL escaping rules. Versions up to 8.1 always treated backslashes as escape
|
||||||
|
* characters in string-literals. Since 8.2, this depends on the value of the
|
||||||
|
* {@code standard_conforming_strings} server variable.
|
||||||
|
*
|
||||||
|
* @return true if the server treats string literals according to the SQL standard
|
||||||
|
*/
|
||||||
|
boolean getStandardConformingStrings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return true if we are going to quote identifier provided in the returning array default is true
|
||||||
|
*/
|
||||||
|
boolean getQuoteReturningIdentifiers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns backend timezone in java format.
|
||||||
|
* @return backend timezone in java format.
|
||||||
|
*/
|
||||||
|
TimeZone getTimeZone();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current encoding in use by this connection
|
||||||
|
*/
|
||||||
|
Encoding getEncoding();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns application_name connection property.
|
||||||
|
* @return application_name connection property
|
||||||
|
*/
|
||||||
|
String getApplicationName();
|
||||||
|
|
||||||
|
boolean isColumnSanitiserDisabled();
|
||||||
|
|
||||||
|
EscapeSyntaxCallMode getEscapeSyntaxCallMode();
|
||||||
|
|
||||||
|
PreferQueryMode getPreferQueryMode();
|
||||||
|
|
||||||
|
void setPreferQueryMode(PreferQueryMode mode);
|
||||||
|
|
||||||
|
AutoSave getAutoSave();
|
||||||
|
|
||||||
|
void setAutoSave(AutoSave autoSave);
|
||||||
|
|
||||||
|
boolean willHealOnRetry(SQLException e);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, the connection resets statement cache in case deallocate all/discard all
|
||||||
|
* message is observed.
|
||||||
|
* This API allows to disable that feature for testing purposes.
|
||||||
|
*
|
||||||
|
* @param flushCacheOnDeallocate true if statement cache should be reset when "deallocate/discard" message observed
|
||||||
|
*/
|
||||||
|
void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ReplicationProtocol instance for this connection.
|
||||||
|
*/
|
||||||
|
ReplicationProtocol getReplicationProtocol();
|
||||||
|
|
||||||
|
void setNetworkTimeout(int milliseconds) throws IOException;
|
||||||
|
|
||||||
|
int getNetworkTimeout() throws IOException;
|
||||||
|
|
||||||
|
// Expose parameter status to PGConnection
|
||||||
|
Map<String, String> getParameterStatuses();
|
||||||
|
|
||||||
|
String getParameterStatus(String parameterName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get fetch size computed by adaptive fetch size for given query.
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch state of adaptive fetch, which should be used during retrieving
|
||||||
|
* @param cursor Cursor used by resultSet, containing query, have to be able to cast to
|
||||||
|
* Portal class.
|
||||||
|
* @return fetch size computed by adaptive fetch size for given query passed inside cursor
|
||||||
|
*/
|
||||||
|
int getAdaptiveFetchSize(boolean adaptiveFetch, ResultCursor cursor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state of adaptive fetch inside QueryExecutor.
|
||||||
|
*
|
||||||
|
* @return state of adaptive fetch inside QueryExecutor
|
||||||
|
*/
|
||||||
|
boolean getAdaptiveFetch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set state of adaptive fetch inside QueryExecutor.
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch desired state of adaptive fetch
|
||||||
|
*/
|
||||||
|
void setAdaptiveFetch(boolean adaptiveFetch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add query to adaptive fetch cache inside QueryExecutor.
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch state of adaptive fetch used during adding query
|
||||||
|
* @param cursor Cursor used by resultSet, containing query, have to be able to cast to
|
||||||
|
* Portal class.
|
||||||
|
*/
|
||||||
|
void addQueryToAdaptiveFetchCache(boolean adaptiveFetch, ResultCursor cursor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove query from adaptive fetch cache inside QueryExecutor
|
||||||
|
*
|
||||||
|
* @param adaptiveFetch state of adaptive fetch used during removing query
|
||||||
|
* @param cursor Cursor used by resultSet, containing query, have to be able to cast to
|
||||||
|
* Portal class.
|
||||||
|
*/
|
||||||
|
void removeQueryFromAdaptiveFetchCache(boolean adaptiveFetch, ResultCursor cursor);
|
||||||
|
}
|
499
pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
Normal file
499
pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
Normal file
|
@ -0,0 +1,499 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGNotification;
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.jdbc.AutoSave;
|
||||||
|
import org.postgresql.jdbc.EscapeSyntaxCallMode;
|
||||||
|
import org.postgresql.jdbc.PreferQueryMode;
|
||||||
|
import org.postgresql.jdbc.ResourceLock;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
import org.postgresql.util.LruCache;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
import org.postgresql.util.ServerErrorMessage;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("try")
|
||||||
|
public abstract class QueryExecutorBase implements QueryExecutor {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(QueryExecutorBase.class.getName());
|
||||||
|
protected final PGStream pgStream;
|
||||||
|
private final String user;
|
||||||
|
private final String database;
|
||||||
|
private final int cancelSignalTimeout;
|
||||||
|
|
||||||
|
private int cancelPid;
|
||||||
|
private int cancelKey;
|
||||||
|
protected final QueryExecutorCloseAction closeAction;
|
||||||
|
private String serverVersion;
|
||||||
|
private int serverVersionNum;
|
||||||
|
private TransactionState transactionState = TransactionState.IDLE;
|
||||||
|
private final boolean reWriteBatchedInserts;
|
||||||
|
private final boolean columnSanitiserDisabled;
|
||||||
|
private final EscapeSyntaxCallMode escapeSyntaxCallMode;
|
||||||
|
private final boolean quoteReturningIdentifiers;
|
||||||
|
private PreferQueryMode preferQueryMode;
|
||||||
|
private AutoSave autoSave;
|
||||||
|
private boolean flushCacheOnDeallocate = true;
|
||||||
|
protected final boolean logServerErrorDetail;
|
||||||
|
|
||||||
|
// default value for server versions that don't report standard_conforming_strings
|
||||||
|
private boolean standardConformingStrings;
|
||||||
|
|
||||||
|
private SQLWarning warnings;
|
||||||
|
private final ArrayList<PGNotification> notifications = new ArrayList<>();
|
||||||
|
|
||||||
|
private final LruCache<Object, CachedQuery> statementCache;
|
||||||
|
private final CachedQueryCreateAction cachedQueryCreateAction;
|
||||||
|
|
||||||
|
// For getParameterStatuses(), GUC_REPORT tracking
|
||||||
|
private final TreeMap<String,String> parameterStatuses
|
||||||
|
= new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
protected final ResourceLock lock = new ResourceLock();
|
||||||
|
protected final Condition lockCondition = lock.newCondition();
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
|
protected QueryExecutorBase(PGStream pgStream, int cancelSignalTimeout, Properties info) throws SQLException {
|
||||||
|
this.pgStream = pgStream;
|
||||||
|
this.user = PGProperty.USER.getOrDefault(info);
|
||||||
|
this.database = PGProperty.PG_DBNAME.getOrDefault(info);
|
||||||
|
this.cancelSignalTimeout = cancelSignalTimeout;
|
||||||
|
this.reWriteBatchedInserts = PGProperty.REWRITE_BATCHED_INSERTS.getBoolean(info);
|
||||||
|
this.columnSanitiserDisabled = PGProperty.DISABLE_COLUMN_SANITISER.getBoolean(info);
|
||||||
|
String callMode = PGProperty.ESCAPE_SYNTAX_CALL_MODE.getOrDefault(info);
|
||||||
|
this.escapeSyntaxCallMode = EscapeSyntaxCallMode.of(callMode);
|
||||||
|
this.quoteReturningIdentifiers = PGProperty.QUOTE_RETURNING_IDENTIFIERS.getBoolean(info);
|
||||||
|
String preferMode = PGProperty.PREFER_QUERY_MODE.getOrDefault(info);
|
||||||
|
this.preferQueryMode = PreferQueryMode.of(preferMode);
|
||||||
|
this.autoSave = AutoSave.of(PGProperty.AUTOSAVE.getOrDefault(info));
|
||||||
|
this.logServerErrorDetail = PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info);
|
||||||
|
// assignment, argument
|
||||||
|
this.cachedQueryCreateAction = new CachedQueryCreateAction(this);
|
||||||
|
statementCache = new LruCache<>(
|
||||||
|
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_QUERIES.getInt(info)),
|
||||||
|
Math.max(0, PGProperty.PREPARED_STATEMENT_CACHE_SIZE_MIB.getInt(info) * 1024L * 1024L),
|
||||||
|
false,
|
||||||
|
cachedQueryCreateAction,
|
||||||
|
new LruCache.EvictAction<CachedQuery>() {
|
||||||
|
@Override
|
||||||
|
public void evict(CachedQuery cachedQuery) throws SQLException {
|
||||||
|
cachedQuery.query.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.closeAction = createCloseAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QueryExecutorCloseAction createCloseAction() {
|
||||||
|
return new QueryExecutorCloseAction(pgStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends "terminate connection" message to the backend.
|
||||||
|
* @throws IOException in case connection termination fails
|
||||||
|
* @deprecated use {@link #getCloseAction()} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
protected abstract void sendCloseMessage() throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNetworkTimeout(int milliseconds) throws IOException {
|
||||||
|
pgStream.setNetworkTimeout(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNetworkTimeout() throws IOException {
|
||||||
|
return pgStream.getNetworkTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HostSpec getHostSpec() {
|
||||||
|
return pgStream.getHostSpec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDatabase() {
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackendKeyData(int cancelPid, int cancelKey) {
|
||||||
|
this.cancelPid = cancelPid;
|
||||||
|
this.cancelKey = cancelKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBackendPID() {
|
||||||
|
return cancelPid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort() {
|
||||||
|
closeAction.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Closeable getCloseAction() {
|
||||||
|
return closeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (closeAction.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
getCloseAction().close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
LOGGER.log(Level.FINEST, "Discarding IOException on close:", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closeAction.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendQueryCancel() throws SQLException {
|
||||||
|
|
||||||
|
PGStream cancelStream = null;
|
||||||
|
|
||||||
|
// Now we need to construct and send a cancel packet
|
||||||
|
try {
|
||||||
|
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> CancelRequest(pid={0},ckey={1})", new Object[]{cancelPid, cancelKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelStream =
|
||||||
|
new PGStream(pgStream.getSocketFactory(), pgStream.getHostSpec(), cancelSignalTimeout);
|
||||||
|
if (cancelSignalTimeout > 0) {
|
||||||
|
cancelStream.setNetworkTimeout(cancelSignalTimeout);
|
||||||
|
}
|
||||||
|
cancelStream.sendInteger4(16);
|
||||||
|
cancelStream.sendInteger2(1234);
|
||||||
|
cancelStream.sendInteger2(5678);
|
||||||
|
cancelStream.sendInteger4(cancelPid);
|
||||||
|
cancelStream.sendInteger4(cancelKey);
|
||||||
|
cancelStream.flush();
|
||||||
|
cancelStream.receiveEOF();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Safe to ignore.
|
||||||
|
LOGGER.log(Level.FINEST, "Ignoring exception on cancel request:", e);
|
||||||
|
} finally {
|
||||||
|
if (cancelStream != null) {
|
||||||
|
try {
|
||||||
|
cancelStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addWarning(SQLWarning newWarning) {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
if (warnings == null) {
|
||||||
|
warnings = newWarning;
|
||||||
|
} else {
|
||||||
|
warnings.setNextWarning(newWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNotification(PGNotification notification) {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
notifications.add(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PGNotification[] getNotifications() throws SQLException {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
PGNotification[] array = notifications.toArray(new PGNotification[0]);
|
||||||
|
notifications.clear();
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarnings() {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
SQLWarning chain = warnings;
|
||||||
|
warnings = null;
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServerVersion() {
|
||||||
|
String serverVersion = this.serverVersion;
|
||||||
|
if (serverVersion == null) {
|
||||||
|
throw new IllegalStateException("serverVersion must not be null");
|
||||||
|
}
|
||||||
|
return serverVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Override
|
||||||
|
public int getServerVersionNum() {
|
||||||
|
if (serverVersionNum != 0) {
|
||||||
|
return serverVersionNum;
|
||||||
|
}
|
||||||
|
return serverVersionNum = Utils.parseServerVersionStr(getServerVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerVersion(String serverVersion) {
|
||||||
|
this.serverVersion = serverVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServerVersionNum(int serverVersionNum) {
|
||||||
|
this.serverVersionNum = serverVersionNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransactionState(TransactionState state) {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
transactionState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStandardConformingStrings(boolean value) {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
standardConformingStrings = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getStandardConformingStrings() {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
return standardConformingStrings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getQuoteReturningIdentifiers() {
|
||||||
|
return quoteReturningIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionState getTransactionState() {
|
||||||
|
try (ResourceLock ignore = lock.obtain()) {
|
||||||
|
return transactionState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncoding(Encoding encoding) throws IOException {
|
||||||
|
pgStream.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Encoding getEncoding() {
|
||||||
|
return pgStream.getEncoding();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReWriteBatchedInsertsEnabled() {
|
||||||
|
return this.reWriteBatchedInserts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CachedQuery borrowQuery(String sql) throws SQLException {
|
||||||
|
return statementCache.borrow(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CachedQuery borrowCallableQuery(String sql) throws SQLException {
|
||||||
|
return statementCache.borrow(new CallableQueryKey(sql));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CachedQuery borrowReturningQuery(String sql, String [] columnNames)
|
||||||
|
throws SQLException {
|
||||||
|
return statementCache.borrow(new QueryWithReturningColumnsKey(sql, true, true,
|
||||||
|
columnNames
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedQuery borrowQueryByKey(Object key) throws SQLException {
|
||||||
|
return statementCache.borrow(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseQuery(CachedQuery cachedQuery) {
|
||||||
|
statementCache.put(cachedQuery.key, cachedQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Object createQueryKey(String sql, boolean escapeProcessing,
|
||||||
|
boolean isParameterized, String ... columnNames) {
|
||||||
|
Object key;
|
||||||
|
if (columnNames == null || columnNames.length != 0) {
|
||||||
|
// Null means "return whatever sensible columns are" (e.g. primary key, or serial, or something like that)
|
||||||
|
key = new QueryWithReturningColumnsKey(sql, isParameterized, escapeProcessing, columnNames);
|
||||||
|
} else if (isParameterized) {
|
||||||
|
// If no generated columns requested, just use the SQL as a cache key
|
||||||
|
key = sql;
|
||||||
|
} else {
|
||||||
|
key = new BaseQueryKey(sql, false, escapeProcessing);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedQuery createQueryByKey(Object key) throws SQLException {
|
||||||
|
return cachedQueryCreateAction.create(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final CachedQuery createQuery(String sql, boolean escapeProcessing,
|
||||||
|
boolean isParameterized, String ... columnNames)
|
||||||
|
throws SQLException {
|
||||||
|
Object key = createQueryKey(sql, escapeProcessing, isParameterized, columnNames);
|
||||||
|
// Note: cache is not reused here for two reasons:
|
||||||
|
// 1) Simplify initial implementation for simple statements
|
||||||
|
// 2) Non-prepared statements are likely to have literals, thus query reuse would not be often
|
||||||
|
return createQueryByKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isColumnSanitiserDisabled() {
|
||||||
|
return columnSanitiserDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EscapeSyntaxCallMode getEscapeSyntaxCallMode() {
|
||||||
|
return escapeSyntaxCallMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PreferQueryMode getPreferQueryMode() {
|
||||||
|
return preferQueryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreferQueryMode(PreferQueryMode mode) {
|
||||||
|
preferQueryMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoSave getAutoSave() {
|
||||||
|
return autoSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutoSave(AutoSave autoSave) {
|
||||||
|
this.autoSave = autoSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean willHealViaReparse(SQLException e) {
|
||||||
|
if (e == null || e.getSQLState() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "prepared statement \"S_2\" does not exist"
|
||||||
|
if (PSQLState.INVALID_SQL_STATEMENT_NAME.getState().equals(e.getSQLState())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!PSQLState.NOT_IMPLEMENTED.getState().equals(e.getSQLState())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(e instanceof PSQLException)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PSQLException pe = (PSQLException) e;
|
||||||
|
|
||||||
|
ServerErrorMessage serverErrorMessage = pe.getServerErrorMessage();
|
||||||
|
if (serverErrorMessage == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// "cached plan must not change result type"
|
||||||
|
String routine = serverErrorMessage.getRoutine();
|
||||||
|
return "RevalidateCachedQuery".equals(routine) // 9.2+
|
||||||
|
|| "RevalidateCachedPlan".equals(routine); // <= 9.1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean willHealOnRetry(SQLException e) {
|
||||||
|
if (autoSave == AutoSave.NEVER && getTransactionState() == TransactionState.FAILED) {
|
||||||
|
// If autorollback is not activated, then every statement will fail with
|
||||||
|
// 'transaction is aborted', etc, etc
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return willHealViaReparse(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlushCacheOnDeallocate() {
|
||||||
|
return flushCacheOnDeallocate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate) {
|
||||||
|
this.flushCacheOnDeallocate = flushCacheOnDeallocate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasNotifications() {
|
||||||
|
return !notifications.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Map<String, String> getParameterStatuses() {
|
||||||
|
return Collections.unmodifiableMap(parameterStatuses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getParameterStatus(String parameterName) {
|
||||||
|
return parameterStatuses.get(parameterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the parameter status map in response to a new ParameterStatus
|
||||||
|
* wire protocol message.
|
||||||
|
*
|
||||||
|
* <p>The server sends ParameterStatus messages when GUC_REPORT settings are
|
||||||
|
* initially assigned and whenever they change.</p>
|
||||||
|
*
|
||||||
|
* <p>A future version may invoke a client-defined listener class at this point,
|
||||||
|
* so this should be the only access path.</p>
|
||||||
|
*
|
||||||
|
* <p>Keys are case-insensitive and case-preserving.</p>
|
||||||
|
*
|
||||||
|
* <p>The server doesn't provide a way to report deletion of a reportable
|
||||||
|
* parameter so we don't expose one here.</p>
|
||||||
|
*
|
||||||
|
* @param parameterName case-insensitive case-preserving name of parameter to create or update
|
||||||
|
* @param parameterStatus new value of parameter
|
||||||
|
* @see org.postgresql.PGConnection#getParameterStatuses
|
||||||
|
* @see org.postgresql.PGConnection#getParameterStatus
|
||||||
|
*/
|
||||||
|
protected void onParameterStatus(String parameterName, String parameterStatus) {
|
||||||
|
if (parameterName == null || "".equals(parameterName)) {
|
||||||
|
throw new IllegalStateException("attempt to set GUC_REPORT parameter with null or empty-string name");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterStatuses.put(parameterName, parameterStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action performs connection cleanup, so it is properly terminated from the backend
|
||||||
|
* point of view.
|
||||||
|
* Implementation note: it should keep only the minimum number of object references
|
||||||
|
* to reduce heap usage in case the user abandons connection without closing it first.
|
||||||
|
*/
|
||||||
|
public class QueryExecutorCloseAction implements Closeable {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(QueryExecutorBase.class.getName());
|
||||||
|
|
||||||
|
@SuppressWarnings("cast")
|
||||||
|
private static final AtomicReferenceFieldUpdater<QueryExecutorCloseAction, PGStream> PG_STREAM_UPDATER =
|
||||||
|
AtomicReferenceFieldUpdater.newUpdater(
|
||||||
|
QueryExecutorCloseAction.class, (Class<PGStream>) PGStream.class, "pgStream");
|
||||||
|
|
||||||
|
private volatile PGStream pgStream;
|
||||||
|
|
||||||
|
public QueryExecutorCloseAction(PGStream pgStream) {
|
||||||
|
this.pgStream = pgStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
PGStream pgStream = this.pgStream;
|
||||||
|
return pgStream == null || pgStream.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abort() {
|
||||||
|
PGStream pgStream = this.pgStream;
|
||||||
|
if (pgStream == null || !PG_STREAM_UPDATER.compareAndSet(this, pgStream, null)) {
|
||||||
|
// The connection has already been closed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> close socket");
|
||||||
|
pgStream.getSocket().close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> Terminate");
|
||||||
|
PGStream pgStream = this.pgStream;
|
||||||
|
if (pgStream == null || !PG_STREAM_UPDATER.compareAndSet(this, pgStream, null)) {
|
||||||
|
// The connection has already been closed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendCloseMessage(pgStream);
|
||||||
|
|
||||||
|
// Technically speaking, this check should not be needed,
|
||||||
|
// however org.postgresql.test.jdbc2.ConnectionTest.testPGStreamSettings
|
||||||
|
// closes pgStream reflectively, so here's an extra check to prevent failures
|
||||||
|
// when getNetworkTimeout is called on a closed stream
|
||||||
|
if (pgStream.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pgStream.flush();
|
||||||
|
pgStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCloseMessage(PGStream pgStream) throws IOException {
|
||||||
|
// Technically speaking, this check should not be needed,
|
||||||
|
// however org.postgresql.test.jdbc2.ConnectionTest.testPGStreamSettings
|
||||||
|
// closes pgStream reflectively, so here's an extra check to prevent failures
|
||||||
|
// when getNetworkTimeout is called on a closed stream
|
||||||
|
if (pgStream.isClosed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Prevent blocking the thread for too long
|
||||||
|
// The connection will be discarded anyway, so there's no much sense in waiting long
|
||||||
|
int timeout = pgStream.getNetworkTimeout();
|
||||||
|
if (timeout == 0 || timeout > 1000) {
|
||||||
|
pgStream.setNetworkTimeout(1000);
|
||||||
|
}
|
||||||
|
pgStream.sendChar('X');
|
||||||
|
pgStream.sendInteger4(4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache key for a query that have some returning columns.
|
||||||
|
* {@code columnNames} should contain non-quoted column names.
|
||||||
|
* The parser will quote them automatically.
|
||||||
|
* <p>There's a special case of {@code columnNames == new String[]{"*"}} that means all columns
|
||||||
|
* should be returned. {@link Parser} is aware of that and does not quote {@code *}</p>
|
||||||
|
*/
|
||||||
|
class QueryWithReturningColumnsKey extends BaseQueryKey {
|
||||||
|
public final String[] columnNames;
|
||||||
|
private int size; // query length cannot exceed MAX_INT
|
||||||
|
|
||||||
|
QueryWithReturningColumnsKey(String sql, boolean isParameterized, boolean escapeProcessing,
|
||||||
|
String [] columnNames) {
|
||||||
|
super(sql, isParameterized, escapeProcessing);
|
||||||
|
if (columnNames == null) {
|
||||||
|
// TODO: teach parser to fetch key columns somehow when no column names were given
|
||||||
|
columnNames = new String[]{"*"};
|
||||||
|
}
|
||||||
|
this.columnNames = columnNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
int size = this.size;
|
||||||
|
if (size != 0) {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
size = (int) super.getSize();
|
||||||
|
if (columnNames != null) {
|
||||||
|
size += 16; // array itself
|
||||||
|
for (String columnName: columnNames) {
|
||||||
|
size += columnName.length() * 2; // 2 bytes per char, revise with Java 9's compact strings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.size = size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "QueryWithReturningColumnsKey{"
|
||||||
|
+ "sql='" + sql + '\''
|
||||||
|
+ ", isParameterized=" + isParameterized
|
||||||
|
+ ", escapeProcessing=" + escapeProcessing
|
||||||
|
+ ", columnNames=" + Arrays.toString(columnNames)
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!super.equals(o)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryWithReturningColumnsKey that = (QueryWithReturningColumnsKey) o;
|
||||||
|
|
||||||
|
// Probably incorrect - comparing Object[] arrays with Arrays.equals
|
||||||
|
return Arrays.equals(columnNames, that.columnNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = 31 * result + Arrays.hashCode(columnNames);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.replication.PGReplicationStream;
|
||||||
|
import org.postgresql.replication.fluent.logical.LogicalReplicationOptions;
|
||||||
|
import org.postgresql.replication.fluent.physical.PhysicalReplicationOptions;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Abstracts the protocol-specific details of physic and logic replication.</p>
|
||||||
|
*
|
||||||
|
* <p>With each connection open with replication options associate own instance ReplicationProtocol.</p>
|
||||||
|
*/
|
||||||
|
public interface ReplicationProtocol {
|
||||||
|
/**
|
||||||
|
* @param options not null options for logical replication stream
|
||||||
|
* @return not null stream instance from which available fetch wal logs that was decode by output
|
||||||
|
* plugin
|
||||||
|
* @throws SQLException on error
|
||||||
|
*/
|
||||||
|
PGReplicationStream startLogical(LogicalReplicationOptions options) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param options not null options for physical replication stream
|
||||||
|
* @return not null stream instance from which available fetch wal logs
|
||||||
|
* @throws SQLException on error
|
||||||
|
*/
|
||||||
|
PGReplicationStream startPhysical(PhysicalReplicationOptions options) throws SQLException;
|
||||||
|
}
|
22
pgjdbc/src/main/java/org/postgresql/core/ResultCursor.java
Normal file
22
pgjdbc/src/main/java/org/postgresql/core/ResultCursor.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction of a cursor over a returned resultset. This is an opaque interface that only provides
|
||||||
|
* a way to close the cursor; all other operations are done by passing a ResultCursor to
|
||||||
|
* QueryExecutor methods.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public interface ResultCursor {
|
||||||
|
/**
|
||||||
|
* Close this cursor. This may not immediately free underlying resources but may make it happen
|
||||||
|
* more promptly. Closed cursors should not be passed to QueryExecutor methods.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
}
|
95
pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
Normal file
95
pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Callback interface for passing query results from the protocol-specific layer to the
|
||||||
|
* protocol-independent JDBC implementation code.</p>
|
||||||
|
*
|
||||||
|
* <p>In general, a single query execution will consist of a number of calls to handleResultRows,
|
||||||
|
* handleCommandStatus, handleWarning, and handleError, followed by a single call to
|
||||||
|
* handleCompletion when query execution is complete. If the caller wants to throw SQLException,
|
||||||
|
* this can be done in handleCompletion.</p>
|
||||||
|
*
|
||||||
|
* <p>Each executed query ends with a call to handleResultRows, handleCommandStatus, or handleError. If
|
||||||
|
* an error occurs, subsequent queries won't generate callbacks.</p>
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
public interface ResultHandler {
|
||||||
|
/**
|
||||||
|
* Called when result rows are received from a query.
|
||||||
|
*
|
||||||
|
* @param fromQuery the underlying query that generated these results; this may not be very
|
||||||
|
* specific (e.g. it may be a query that includes multiple statements).
|
||||||
|
* @param fields column metadata for the resultset; might be <code>null</code> if
|
||||||
|
* Query.QUERY_NO_METADATA was specified.
|
||||||
|
* @param tuples the actual data
|
||||||
|
* @param cursor a cursor to use to fetch additional data; <code>null</code> if no further results
|
||||||
|
* are present.
|
||||||
|
*/
|
||||||
|
void handleResultRows(Query fromQuery, Field[] fields, List<Tuple> tuples,
|
||||||
|
ResultCursor cursor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a query that did not return a resultset completes.
|
||||||
|
*
|
||||||
|
* @param status the command status string (e.g. "SELECT") returned by the backend
|
||||||
|
* @param updateCount the number of rows affected by an INSERT, UPDATE, DELETE, FETCH, or MOVE
|
||||||
|
* command; -1 if not available.
|
||||||
|
* @param insertOID for a single-row INSERT query, the OID of the newly inserted row; 0 if not
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
void handleCommandStatus(String status, long updateCount, long insertOID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a warning is emitted.
|
||||||
|
*
|
||||||
|
* @param warning the warning that occurred.
|
||||||
|
*/
|
||||||
|
void handleWarning(SQLWarning warning);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an error occurs. Subsequent queries are abandoned; in general the only calls
|
||||||
|
* between a handleError call and a subsequent handleCompletion call are handleError or
|
||||||
|
* handleWarning.
|
||||||
|
*
|
||||||
|
* @param error the error that occurred
|
||||||
|
*/
|
||||||
|
void handleError(SQLException error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before a QueryExecutor method returns. This method may throw a SQLException if desired;
|
||||||
|
* if it does, the QueryExecutor method will propagate that exception to the original caller.
|
||||||
|
*
|
||||||
|
* @throws SQLException if the handler wishes the original method to throw an exception.
|
||||||
|
*/
|
||||||
|
void handleCompletion() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for batch statements. In case batch statement is executed in autocommit==true mode,
|
||||||
|
* the executor might commit "as it this it is best", so the result handler should track which
|
||||||
|
* statements are executed successfully and which are not.
|
||||||
|
*/
|
||||||
|
void secureProgress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first encountered exception. The rest are chained via {@link SQLException#setNextException(SQLException)}
|
||||||
|
* @return the first encountered exception
|
||||||
|
*/
|
||||||
|
SQLException getException();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first encountered warning. The rest are chained via {@link SQLException#setNextException(SQLException)}
|
||||||
|
* @return the first encountered warning
|
||||||
|
*/
|
||||||
|
SQLWarning getWarning();
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty implementation of {@link ResultHandler} interface.
|
||||||
|
* {@link SQLException#setNextException(SQLException)} has {@code O(N)} complexity,
|
||||||
|
* so this class tracks the last exception object to speedup {@code setNextException}.
|
||||||
|
*/
|
||||||
|
public class ResultHandlerBase implements ResultHandler {
|
||||||
|
// Last exception is tracked to avoid O(N) SQLException#setNextException just in case there
|
||||||
|
// will be lots of exceptions (e.g. all batch rows fail with constraint violation or so)
|
||||||
|
private SQLException firstException;
|
||||||
|
private SQLException lastException;
|
||||||
|
|
||||||
|
private SQLWarning firstWarning;
|
||||||
|
private SQLWarning lastWarning;
|
||||||
|
|
||||||
|
public ResultHandlerBase() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResultRows(Query fromQuery, Field[] fields, List<Tuple> tuples,
|
||||||
|
ResultCursor cursor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommandStatus(String status, long updateCount, long insertOID) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void secureProgress() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleWarning(SQLWarning warning) {
|
||||||
|
if (firstWarning == null) {
|
||||||
|
firstWarning = lastWarning = warning;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SQLWarning lastWarning = this.lastWarning;
|
||||||
|
lastWarning.setNextException(warning);
|
||||||
|
this.lastWarning = warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(SQLException error) {
|
||||||
|
if (firstException == null) {
|
||||||
|
firstException = lastException = error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastException.setNextException(error);
|
||||||
|
this.lastException = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCompletion() throws SQLException {
|
||||||
|
SQLException firstException = this.firstException;
|
||||||
|
if (firstException != null) {
|
||||||
|
throw firstException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLException getException() {
|
||||||
|
return firstException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarning() {
|
||||||
|
return firstWarning;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal to the driver class, please do not use in the application.
|
||||||
|
*
|
||||||
|
* <p>The class simplifies creation of ResultHandler delegates: it provides default implementation
|
||||||
|
* for the interface methods</p>
|
||||||
|
*/
|
||||||
|
public class ResultHandlerDelegate implements ResultHandler {
|
||||||
|
private final ResultHandler delegate;
|
||||||
|
|
||||||
|
public ResultHandlerDelegate(ResultHandler delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResultRows(Query fromQuery, Field[] fields, List<Tuple> tuples,
|
||||||
|
ResultCursor cursor) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.handleResultRows(fromQuery, fields, tuples, cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommandStatus(String status, long updateCount, long insertOID) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.handleCommandStatus(status, updateCount, insertOID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleWarning(SQLWarning warning) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.handleWarning(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleError(SQLException error) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCompletion() throws SQLException {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.handleCompletion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void secureProgress() {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.secureProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLException getException() {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.getException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SQLWarning getWarning() {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.getWarning();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
185
pgjdbc/src/main/java/org/postgresql/core/ServerVersion.java
Normal file
185
pgjdbc/src/main/java/org/postgresql/core/ServerVersion.java
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.text.ParsePosition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration for PostgreSQL versions.
|
||||||
|
*/
|
||||||
|
public enum ServerVersion implements Version {
|
||||||
|
|
||||||
|
INVALID("0.0.0"),
|
||||||
|
v8_2("8.2.0"),
|
||||||
|
v8_3("8.3.0"),
|
||||||
|
v8_4("8.4.0"),
|
||||||
|
v9_0("9.0.0"),
|
||||||
|
v9_1("9.1.0"),
|
||||||
|
v9_2("9.2.0"),
|
||||||
|
v9_3("9.3.0"),
|
||||||
|
v9_4("9.4.0"),
|
||||||
|
v9_5("9.5.0"),
|
||||||
|
v9_6("9.6.0"),
|
||||||
|
v10("10"),
|
||||||
|
v11("11"),
|
||||||
|
v12("12"),
|
||||||
|
v13("13"),
|
||||||
|
v14("14"),
|
||||||
|
v15("15"),
|
||||||
|
v16("16")
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int version;
|
||||||
|
|
||||||
|
ServerVersion(String version) {
|
||||||
|
this.version = parseServerVersionStr(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a machine-readable version number.
|
||||||
|
*
|
||||||
|
* @return the version in numeric XXYYZZ form, e.g. 90401 for 9.4.1
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getVersionNum() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Attempt to parse the server version string into an XXYYZZ form version number into a
|
||||||
|
* {@link Version}.</p>
|
||||||
|
*
|
||||||
|
* <p>If the specified version cannot be parsed, the {@link Version#getVersionNum()} will return 0.</p>
|
||||||
|
*
|
||||||
|
* @param version version in numeric XXYYZZ form, e.g. "090401" for 9.4.1
|
||||||
|
* @return a {@link Version} representing the specified version string.
|
||||||
|
*/
|
||||||
|
public static Version from(String version) {
|
||||||
|
final int versionNum = parseServerVersionStr(version);
|
||||||
|
return new Version() {
|
||||||
|
@Override
|
||||||
|
public int getVersionNum() {
|
||||||
|
return versionNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof Version) {
|
||||||
|
return this.getVersionNum() == ((Version) obj).getVersionNum();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getVersionNum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Integer.toString(versionNum);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Attempt to parse the server version string into an XXYYZZ form version number.</p>
|
||||||
|
*
|
||||||
|
* <p>Returns 0 if the version could not be parsed.</p>
|
||||||
|
*
|
||||||
|
* <p>Returns minor version 0 if the minor version could not be determined, e.g. devel or beta
|
||||||
|
* releases.</p>
|
||||||
|
*
|
||||||
|
* <p>If a single major part like 90400 is passed, it's assumed to be a pre-parsed version and
|
||||||
|
* returned verbatim. (Anything equal to or greater than 10000 is presumed to be this form).</p>
|
||||||
|
*
|
||||||
|
* <p>The yy or zz version parts may be larger than 99. A NumberFormatException is thrown if a
|
||||||
|
* version part is out of range.</p>
|
||||||
|
*
|
||||||
|
* @param serverVersion server version in a XXYYZZ form
|
||||||
|
* @return server version in number form
|
||||||
|
*/
|
||||||
|
static int parseServerVersionStr(String serverVersion) throws NumberFormatException {
|
||||||
|
if (serverVersion == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberFormat numformat = NumberFormat.getIntegerInstance();
|
||||||
|
numformat.setGroupingUsed(false);
|
||||||
|
ParsePosition parsepos = new ParsePosition(0);
|
||||||
|
|
||||||
|
int[] parts = new int[3];
|
||||||
|
int versionParts;
|
||||||
|
for (versionParts = 0; versionParts < 3; versionParts++) {
|
||||||
|
Number part = (Number) numformat.parseObject(serverVersion, parsepos);
|
||||||
|
if (part == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parts[versionParts] = part.intValue();
|
||||||
|
if (parsepos.getIndex() == serverVersion.length()
|
||||||
|
|| serverVersion.charAt(parsepos.getIndex()) != '.') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Skip .
|
||||||
|
parsepos.setIndex(parsepos.getIndex() + 1);
|
||||||
|
}
|
||||||
|
versionParts++;
|
||||||
|
|
||||||
|
if (parts[0] >= 10000) {
|
||||||
|
/*
|
||||||
|
* PostgreSQL version 1000? I don't think so. We're seeing a version like 90401; return it
|
||||||
|
* verbatim, but only if there's nothing else in the version. If there is, treat it as a parse
|
||||||
|
* error.
|
||||||
|
*/
|
||||||
|
if (parsepos.getIndex() == serverVersion.length() && versionParts == 1) {
|
||||||
|
return parts[0];
|
||||||
|
} else {
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"First major-version part equal to or greater than 10000 in invalid version string: "
|
||||||
|
+ serverVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #667 - Allow for versions with greater than 3 parts.
|
||||||
|
For versions with more than 3 parts, still return 3 parts (4th part ignored for now
|
||||||
|
as no functionality is dependent on the 4th part .
|
||||||
|
Allows for future versions of the server to utilize more than 3 part version numbers
|
||||||
|
without upgrading the jdbc driver */
|
||||||
|
|
||||||
|
if (versionParts >= 3) {
|
||||||
|
if (parts[1] > 99) {
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"Unsupported second part of major version > 99 in invalid version string: "
|
||||||
|
+ serverVersion);
|
||||||
|
}
|
||||||
|
if (parts[2] > 99) {
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"Unsupported second part of minor version > 99 in invalid version string: "
|
||||||
|
+ serverVersion);
|
||||||
|
}
|
||||||
|
return (parts[0] * 100 + parts[1]) * 100 + parts[2];
|
||||||
|
}
|
||||||
|
if (versionParts == 2) {
|
||||||
|
if (parts[0] >= 10) {
|
||||||
|
return parts[0] * 100 * 100 + parts[1];
|
||||||
|
}
|
||||||
|
if (parts[1] > 99) {
|
||||||
|
throw new NumberFormatException(
|
||||||
|
"Unsupported second part of major version > 99 in invalid version string: "
|
||||||
|
+ serverVersion);
|
||||||
|
}
|
||||||
|
return (parts[0] * 100 + parts[1]) * 100;
|
||||||
|
}
|
||||||
|
if (versionParts == 1) {
|
||||||
|
if (parts[0] >= 10) {
|
||||||
|
return parts[0] * 100 * 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; /* unknown */
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.SQLWarning;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poor man's Statement & ResultSet, used for initial queries while we're still initializing the
|
||||||
|
* system.
|
||||||
|
*/
|
||||||
|
public class SetupQueryRunner {
|
||||||
|
|
||||||
|
public SetupQueryRunner() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SimpleResultHandler extends ResultHandlerBase {
|
||||||
|
private List<Tuple> tuples;
|
||||||
|
|
||||||
|
List<Tuple> getResults() {
|
||||||
|
return tuples;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResultRows(Query fromQuery, Field[] fields, List<Tuple> tuples,
|
||||||
|
ResultCursor cursor) {
|
||||||
|
this.tuples = tuples;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleWarning(SQLWarning warning) {
|
||||||
|
// We ignore warnings. We assume we know what we're
|
||||||
|
// doing in the setup queries.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tuple run(QueryExecutor executor, String queryString,
|
||||||
|
boolean wantResults) throws SQLException {
|
||||||
|
Query query = executor.createSimpleQuery(queryString);
|
||||||
|
SimpleResultHandler handler = new SimpleResultHandler();
|
||||||
|
|
||||||
|
int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_SUPPRESS_BEGIN
|
||||||
|
| QueryExecutor.QUERY_EXECUTE_AS_SIMPLE;
|
||||||
|
if (!wantResults) {
|
||||||
|
flags |= QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_NO_METADATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor.execute(query, null, handler, 0, 0, flags);
|
||||||
|
} finally {
|
||||||
|
query.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wantResults) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Tuple> tuples = handler.getResults();
|
||||||
|
if (tuples == null || tuples.size() != 1) {
|
||||||
|
throw new PSQLException(GT.tr("An unexpected result was returned by a query."),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tuples.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.ssl.LibPQFactory;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.ObjectFactory;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates {@link SocketFactory} based on the {@link PGProperty#SOCKET_FACTORY}.
|
||||||
|
*/
|
||||||
|
public class SocketFactoryFactory {
|
||||||
|
|
||||||
|
public SocketFactoryFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates {@link SocketFactory} based on the {@link PGProperty#SOCKET_FACTORY}.
|
||||||
|
*
|
||||||
|
* @param info connection properties
|
||||||
|
* @return socket factory
|
||||||
|
* @throws PSQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
public static SocketFactory getSocketFactory(Properties info) throws PSQLException {
|
||||||
|
// Socket factory
|
||||||
|
String socketFactoryClassName = PGProperty.SOCKET_FACTORY.getOrDefault(info);
|
||||||
|
if (socketFactoryClassName == null) {
|
||||||
|
return SocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ObjectFactory.instantiate(SocketFactory.class, socketFactoryClassName, info, true,
|
||||||
|
PGProperty.SOCKET_FACTORY_ARG.getOrDefault(info));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The SocketFactory class provided {0} could not be instantiated.",
|
||||||
|
socketFactoryClassName),
|
||||||
|
PSQLState.CONNECTION_FAILURE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates {@link SSLSocketFactory} based on the {@link PGProperty#SSL_FACTORY}.
|
||||||
|
*
|
||||||
|
* @param info connection properties
|
||||||
|
* @return SSL socket factory
|
||||||
|
* @throws PSQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static SSLSocketFactory getSslSocketFactory(Properties info) throws PSQLException {
|
||||||
|
String classname = PGProperty.SSL_FACTORY.getOrDefault(info);
|
||||||
|
if (classname == null
|
||||||
|
|| "org.postgresql.ssl.jdbc4.LibPQFactory".equals(classname)
|
||||||
|
|| "org.postgresql.ssl.LibPQFactory".equals(classname)) {
|
||||||
|
return new LibPQFactory(info);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ObjectFactory.instantiate(SSLSocketFactory.class, classname, info, true,
|
||||||
|
PGProperty.SSL_FACTORY_ARG.getOrDefault(info));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The SSLSocketFactory class provided {0} could not be instantiated.", classname),
|
||||||
|
PSQLState.CONNECTION_FAILURE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
81
pgjdbc/src/main/java/org/postgresql/core/SqlCommand.java
Normal file
81
pgjdbc/src/main/java/org/postgresql/core/SqlCommand.java
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import static org.postgresql.core.SqlCommandType.INSERT;
|
||||||
|
import static org.postgresql.core.SqlCommandType.SELECT;
|
||||||
|
import static org.postgresql.core.SqlCommandType.WITH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Modification Language inspection support.
|
||||||
|
*
|
||||||
|
* @author Jeremy Whiting jwhiting@redhat.com
|
||||||
|
* @author Christopher Deckers (chrriis@gmail.com)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SqlCommand {
|
||||||
|
public static final SqlCommand BLANK = SqlCommand.createStatementTypeInfo(SqlCommandType.BLANK);
|
||||||
|
|
||||||
|
public boolean isBatchedReWriteCompatible() {
|
||||||
|
return valuesBraceOpenPosition >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatchRewriteValuesBraceOpenPosition() {
|
||||||
|
return valuesBraceOpenPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBatchRewriteValuesBraceClosePosition() {
|
||||||
|
return valuesBraceClosePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlCommandType getType() {
|
||||||
|
return commandType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReturningKeywordPresent() {
|
||||||
|
return parsedSQLhasRETURNINGKeyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean returnsRows() {
|
||||||
|
return parsedSQLhasRETURNINGKeyword || commandType == SELECT || commandType == WITH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlCommand createStatementTypeInfo(SqlCommandType type,
|
||||||
|
boolean isBatchedReWritePropertyConfigured,
|
||||||
|
int valuesBraceOpenPosition, int valuesBraceClosePosition, boolean isRETURNINGkeywordPresent,
|
||||||
|
int priorQueryCount) {
|
||||||
|
return new SqlCommand(type, isBatchedReWritePropertyConfigured,
|
||||||
|
valuesBraceOpenPosition, valuesBraceClosePosition, isRETURNINGkeywordPresent,
|
||||||
|
priorQueryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlCommand createStatementTypeInfo(SqlCommandType type) {
|
||||||
|
return new SqlCommand(type, false, -1, -1, false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SqlCommand createStatementTypeInfo(SqlCommandType type,
|
||||||
|
boolean isRETURNINGkeywordPresent) {
|
||||||
|
return new SqlCommand(type, false, -1, -1, isRETURNINGkeywordPresent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlCommand(SqlCommandType type, boolean isBatchedReWriteConfigured,
|
||||||
|
int valuesBraceOpenPosition, int valuesBraceClosePosition, boolean isPresent,
|
||||||
|
int priorQueryCount) {
|
||||||
|
commandType = type;
|
||||||
|
parsedSQLhasRETURNINGKeyword = isPresent;
|
||||||
|
boolean batchedReWriteCompatible = (type == INSERT) && isBatchedReWriteConfigured
|
||||||
|
&& valuesBraceOpenPosition >= 0 && valuesBraceClosePosition > valuesBraceOpenPosition
|
||||||
|
&& !isPresent && priorQueryCount == 0;
|
||||||
|
this.valuesBraceOpenPosition = batchedReWriteCompatible ? valuesBraceOpenPosition : -1;
|
||||||
|
this.valuesBraceClosePosition = batchedReWriteCompatible ? valuesBraceClosePosition : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SqlCommandType commandType;
|
||||||
|
private final boolean parsedSQLhasRETURNINGKeyword;
|
||||||
|
private final int valuesBraceOpenPosition;
|
||||||
|
private final int valuesBraceClosePosition;
|
||||||
|
|
||||||
|
}
|
29
pgjdbc/src/main/java/org/postgresql/core/SqlCommandType.java
Normal file
29
pgjdbc/src/main/java/org/postgresql/core/SqlCommandType.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type information inspection support.
|
||||||
|
* @author Jeremy Whiting jwhiting@redhat.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public enum SqlCommandType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use BLANK for empty sql queries or when parsing the sql string is not
|
||||||
|
* necessary.
|
||||||
|
*/
|
||||||
|
BLANK,
|
||||||
|
INSERT,
|
||||||
|
UPDATE,
|
||||||
|
DELETE,
|
||||||
|
MOVE,
|
||||||
|
SELECT,
|
||||||
|
WITH,
|
||||||
|
CREATE,
|
||||||
|
ALTER
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
public enum TransactionState {
|
||||||
|
IDLE,
|
||||||
|
OPEN,
|
||||||
|
FAILED
|
||||||
|
}
|
100
pgjdbc/src/main/java/org/postgresql/core/Tuple.java
Normal file
100
pgjdbc/src/main/java/org/postgresql/core/Tuple.java
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a row in a {@link java.sql.ResultSet}.
|
||||||
|
*/
|
||||||
|
public class Tuple {
|
||||||
|
private final boolean forUpdate;
|
||||||
|
final byte[] [] data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an empty tuple. Used in updatable result sets.
|
||||||
|
* @param length the number of fields in the tuple.
|
||||||
|
*/
|
||||||
|
public Tuple(int length) {
|
||||||
|
this(new byte[length][], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a populated tuple. Used when returning results.
|
||||||
|
* @param data the tuple data
|
||||||
|
*/
|
||||||
|
public Tuple(byte[] [] data) {
|
||||||
|
this(data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple(byte[] [] data, boolean forUpdate) {
|
||||||
|
this.data = data;
|
||||||
|
this.forUpdate = forUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of fields in the tuple
|
||||||
|
* @return number of fields
|
||||||
|
*/
|
||||||
|
public int fieldCount() {
|
||||||
|
return data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total length in bytes of the tuple data.
|
||||||
|
* @return the number of bytes in this tuple
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
int length = 0;
|
||||||
|
for (byte[] field : data) {
|
||||||
|
if (field != null) {
|
||||||
|
length += field.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data for the given field
|
||||||
|
* @param index 0-based field position in the tuple
|
||||||
|
* @return byte array of the data
|
||||||
|
*/
|
||||||
|
public byte [] get(int index) {
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a copy of the tuple for updating.
|
||||||
|
* @return a copy of the tuple that allows updates
|
||||||
|
*/
|
||||||
|
public Tuple updateableCopy() {
|
||||||
|
return copy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a read-only copy of the tuple
|
||||||
|
* @return a copy of the tuple that does not allow updates
|
||||||
|
*/
|
||||||
|
public Tuple readOnlyCopy() {
|
||||||
|
return copy(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple copy(boolean forUpdate) {
|
||||||
|
byte[][] dataCopy = new byte[data.length][];
|
||||||
|
System.arraycopy(data, 0, dataCopy, 0, data.length);
|
||||||
|
return new Tuple(dataCopy, forUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the given field to the given data.
|
||||||
|
* @param index 0-based field position
|
||||||
|
* @param fieldData the data to set
|
||||||
|
*/
|
||||||
|
public void set(int index, byte [] fieldData) {
|
||||||
|
if (!forUpdate) {
|
||||||
|
throw new IllegalArgumentException("Attempted to write to readonly tuple");
|
||||||
|
}
|
||||||
|
data[index] = fieldData;
|
||||||
|
}
|
||||||
|
}
|
145
pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java
Normal file
145
pgjdbc/src/main/java/org/postgresql/core/TypeInfo.java
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2008, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.PGobject;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public interface TypeInfo {
|
||||||
|
void addCoreType(String pgTypeName, Integer oid, Integer sqlType, String javaClass,
|
||||||
|
Integer arrayOid);
|
||||||
|
|
||||||
|
void addDataType(String type, Class<? extends PGobject> klass) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the SQL typecode for a given type oid.
|
||||||
|
*
|
||||||
|
* @param oid the type's OID
|
||||||
|
* @return the SQL type code (a constant from {@link java.sql.Types}) for the type
|
||||||
|
* @throws SQLException if an error occurs when retrieving sql type
|
||||||
|
*/
|
||||||
|
int getSQLType(int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the SQL typecode for a given postgresql type name.
|
||||||
|
*
|
||||||
|
* @param pgTypeName the server type name to look up
|
||||||
|
* @return the SQL type code (a constant from {@link java.sql.Types}) for the type
|
||||||
|
* @throws SQLException if an error occurs when retrieving sql type
|
||||||
|
*/
|
||||||
|
int getSQLType(String pgTypeName) throws SQLException;
|
||||||
|
|
||||||
|
int getJavaArrayType(String className) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the oid for a given postgresql type name. This is the inverse of
|
||||||
|
* {@link #getPGType(int)}.
|
||||||
|
*
|
||||||
|
* @param pgTypeName the server type name to look up
|
||||||
|
* @return the type's OID, or 0 if unknown
|
||||||
|
* @throws SQLException if an error occurs when retrieving PG type
|
||||||
|
*/
|
||||||
|
int getPGType(String pgTypeName) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the postgresql type name for a given oid. This is the inverse of
|
||||||
|
* {@link #getPGType(String)}.
|
||||||
|
*
|
||||||
|
* @param oid the type's OID
|
||||||
|
* @return the server type name for that OID or null if unknown
|
||||||
|
* @throws SQLException if an error occurs when retrieving PG type
|
||||||
|
*/
|
||||||
|
String getPGType(int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the oid of an array's base type given the array's type oid.
|
||||||
|
*
|
||||||
|
* @param oid the array type's OID
|
||||||
|
* @return the base type's OID, or 0 if unknown
|
||||||
|
* @throws SQLException if an error occurs when retrieving array element
|
||||||
|
*/
|
||||||
|
int getPGArrayElement(int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the oid of the given base postgresql type's array type.
|
||||||
|
*
|
||||||
|
* @param elementTypeName the base type's
|
||||||
|
* @return the array type's OID, or 0 if unknown
|
||||||
|
* @throws SQLException if an error occurs when retrieving array type
|
||||||
|
*/
|
||||||
|
int getPGArrayType(String elementTypeName) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the delimiter for the elements of the given array type oid.
|
||||||
|
*
|
||||||
|
* @param oid the array type's OID
|
||||||
|
* @return the base type's array type delimiter
|
||||||
|
* @throws SQLException if an error occurs when retrieving array delimiter
|
||||||
|
*/
|
||||||
|
char getArrayDelimiter(int oid) throws SQLException;
|
||||||
|
|
||||||
|
Iterator<String> getPGTypeNamesWithSQLTypes();
|
||||||
|
|
||||||
|
Iterator<Integer> getPGTypeOidsWithSQLTypes();
|
||||||
|
|
||||||
|
Class<? extends PGobject> getPGobject(String type);
|
||||||
|
|
||||||
|
String getJavaClass(int oid) throws SQLException;
|
||||||
|
|
||||||
|
String getTypeForAlias(String alias);
|
||||||
|
|
||||||
|
int getPrecision(int oid, int typmod);
|
||||||
|
|
||||||
|
int getScale(int oid, int typmod);
|
||||||
|
|
||||||
|
boolean isCaseSensitive(int oid);
|
||||||
|
|
||||||
|
boolean isSigned(int oid);
|
||||||
|
|
||||||
|
int getDisplaySize(int oid, int typmod);
|
||||||
|
|
||||||
|
int getMaximumPrecision(int oid);
|
||||||
|
|
||||||
|
boolean requiresQuoting(int oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if particular sqlType requires quoting.
|
||||||
|
* This method is used internally by the driver, so it might disappear without notice.
|
||||||
|
*
|
||||||
|
* @param sqlType sql type as in java.sql.Types
|
||||||
|
* @return true if the type requires quoting
|
||||||
|
* @throws SQLException if something goes wrong
|
||||||
|
*/
|
||||||
|
boolean requiresQuotingSqlType(int sqlType) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
|
||||||
|
* We therefore read them as positive long values and then force them into signed integers
|
||||||
|
* (wrapping around into negative values when required) or we'd be unable to correctly
|
||||||
|
* handle the upper half of the oid space.</p>
|
||||||
|
*
|
||||||
|
* <p>This function handles the mapping of uint32-values in the long to java integers, and
|
||||||
|
* throws for values that are out of range.</p>
|
||||||
|
*
|
||||||
|
* @param oid the oid as a long.
|
||||||
|
* @return the (internal) signed integer representation of the (unsigned) oid.
|
||||||
|
* @throws SQLException if the long has a value outside of the range representable by uint32
|
||||||
|
*/
|
||||||
|
int longOidToInt(long oid) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java Integers are signed 32-bit integers, but oids are unsigned 32-bit integers.
|
||||||
|
* We must therefore first map the (internal) integer representation to a positive long
|
||||||
|
* value before sending it to postgresql, or we would be unable to correctly handle the
|
||||||
|
* upper half of the oid space because these negative values are disallowed as OID values.
|
||||||
|
*
|
||||||
|
* @param oid the (signed) integer oid to convert into a long.
|
||||||
|
* @return the non-negative value of this oid, stored as a java long.
|
||||||
|
*/
|
||||||
|
long intOidToLong(int oid);
|
||||||
|
}
|
180
pgjdbc/src/main/java/org/postgresql/core/Utils.java
Normal file
180
pgjdbc/src/main/java/org/postgresql/core/Utils.java
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of utilities used by the protocol-level code.
|
||||||
|
*/
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
public Utils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a bytearray into a printable form, representing each byte in hex.
|
||||||
|
*
|
||||||
|
* @param data the bytearray to stringize
|
||||||
|
* @return a hex-encoded printable representation of {@code data}
|
||||||
|
*/
|
||||||
|
public static String toHexString(byte[] data) {
|
||||||
|
StringBuilder sb = new StringBuilder(data.length * 2);
|
||||||
|
for (byte element : data) {
|
||||||
|
sb.append(Integer.toHexString((element >> 4) & 15));
|
||||||
|
sb.append(Integer.toHexString(element & 15));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape the given literal {@code value} and append it to the string builder {@code sbuf}. If
|
||||||
|
* {@code sbuf} is {@code null}, a new StringBuilder will be returned. The argument
|
||||||
|
* {@code standardConformingStrings} defines whether the backend expects standard-conforming
|
||||||
|
* string literals or allows backslash escape sequences.
|
||||||
|
*
|
||||||
|
* @param sbuf the string builder to append to; or {@code null}
|
||||||
|
* @param value the string value
|
||||||
|
* @param standardConformingStrings if standard conforming strings should be used
|
||||||
|
* @return the sbuf argument; or a new string builder for sbuf == null
|
||||||
|
* @throws SQLException if the string contains a {@code \0} character
|
||||||
|
*/
|
||||||
|
public static StringBuilder escapeLiteral(StringBuilder sbuf, String value,
|
||||||
|
boolean standardConformingStrings) throws SQLException {
|
||||||
|
if (sbuf == null) {
|
||||||
|
sbuf = new StringBuilder((value.length() + 10) / 10 * 11); // Add 10% for escaping.
|
||||||
|
}
|
||||||
|
doAppendEscapedLiteral(sbuf, value, standardConformingStrings);
|
||||||
|
return sbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common part for {@link #escapeLiteral(StringBuilder, String, boolean)}.
|
||||||
|
*
|
||||||
|
* @param sbuf Either StringBuffer or StringBuilder as we do not expect any IOException to be
|
||||||
|
* thrown
|
||||||
|
* @param value value to append
|
||||||
|
* @param standardConformingStrings if standard conforming strings should be used
|
||||||
|
*/
|
||||||
|
private static void doAppendEscapedLiteral(Appendable sbuf, String value,
|
||||||
|
boolean standardConformingStrings) throws SQLException {
|
||||||
|
try {
|
||||||
|
if (standardConformingStrings) {
|
||||||
|
// With standard_conforming_strings on, escape only single-quotes.
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
if (ch == '\0') {
|
||||||
|
throw new PSQLException(GT.tr("Zero bytes may not occur in string parameters."),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
if (ch == '\'') {
|
||||||
|
sbuf.append('\'');
|
||||||
|
}
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// With standard_conforming_string off, escape backslashes and
|
||||||
|
// single-quotes, but still escape single-quotes by doubling, to
|
||||||
|
// avoid a security hazard if the reported value of
|
||||||
|
// standard_conforming_strings is incorrect, or an error if
|
||||||
|
// backslash_quote is off.
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
if (ch == '\0') {
|
||||||
|
throw new PSQLException(GT.tr("Zero bytes may not occur in string parameters."),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
if (ch == '\\' || ch == '\'') {
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PSQLException(GT.tr("No IOException expected from StringBuffer or StringBuilder"),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape the given identifier {@code value} and append it to the string builder {@code sbuf}.
|
||||||
|
* If {@code sbuf} is {@code null}, a new StringBuilder will be returned. This method is
|
||||||
|
* different from appendEscapedLiteral in that it includes the quoting required for the identifier
|
||||||
|
* while {@link #escapeLiteral(StringBuilder, String, boolean)} does not.
|
||||||
|
*
|
||||||
|
* @param sbuf the string builder to append to; or {@code null}
|
||||||
|
* @param value the string value
|
||||||
|
* @return the sbuf argument; or a new string builder for sbuf == null
|
||||||
|
* @throws SQLException if the string contains a {@code \0} character
|
||||||
|
*/
|
||||||
|
public static StringBuilder escapeIdentifier(StringBuilder sbuf, String value)
|
||||||
|
throws SQLException {
|
||||||
|
if (sbuf == null) {
|
||||||
|
sbuf = new StringBuilder(2 + (value.length() + 10) / 10 * 11); // Add 10% for escaping.
|
||||||
|
}
|
||||||
|
doAppendEscapedIdentifier(sbuf, value);
|
||||||
|
return sbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common part for appendEscapedIdentifier.
|
||||||
|
*
|
||||||
|
* @param sbuf Either StringBuffer or StringBuilder as we do not expect any IOException to be
|
||||||
|
* thrown.
|
||||||
|
* @param value value to append
|
||||||
|
*/
|
||||||
|
private static void doAppendEscapedIdentifier(Appendable sbuf, String value) throws SQLException {
|
||||||
|
try {
|
||||||
|
sbuf.append('"');
|
||||||
|
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
if (ch == '\0') {
|
||||||
|
throw new PSQLException(GT.tr("Zero bytes may not occur in identifiers."),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
if (ch == '"') {
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
sbuf.append(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
sbuf.append('"');
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PSQLException(GT.tr("No IOException expected from StringBuffer or StringBuilder"),
|
||||||
|
PSQLState.UNEXPECTED_ERROR, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Attempt to parse the server version string into an XXYYZZ form version number.</p>
|
||||||
|
*
|
||||||
|
* <p>Returns 0 if the version could not be parsed.</p>
|
||||||
|
*
|
||||||
|
* <p>Returns minor version 0 if the minor version could not be determined, e.g. devel or beta
|
||||||
|
* releases.</p>
|
||||||
|
*
|
||||||
|
* <p>If a single major part like 90400 is passed, it's assumed to be a pre-parsed version and
|
||||||
|
* returned verbatim. (Anything equal to or greater than 10000 is presumed to be this form).</p>
|
||||||
|
*
|
||||||
|
* <p>The yy or zz version parts may be larger than 99. A NumberFormatException is thrown if a
|
||||||
|
* version part is out of range.</p>
|
||||||
|
*
|
||||||
|
* @param serverVersion server version in a XXYYZZ form
|
||||||
|
* @return server version in number form
|
||||||
|
* @deprecated use specific {@link Version} instance
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static int parseServerVersionStr(String serverVersion) throws NumberFormatException {
|
||||||
|
return ServerVersion.parseServerVersionStr(serverVersion);
|
||||||
|
}
|
||||||
|
}
|
17
pgjdbc/src/main/java/org/postgresql/core/Version.java
Normal file
17
pgjdbc/src/main/java/org/postgresql/core/Version.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
public interface Version {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a machine-readable version number.
|
||||||
|
*
|
||||||
|
* @return the version in numeric XXYYZZ form, e.g. 90401 for 9.4.1
|
||||||
|
*/
|
||||||
|
int getVersionNum();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,356 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2006, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A faster version of BufferedInputStream. Does no synchronisation and allows direct access to the
|
||||||
|
* used byte[] buffer.
|
||||||
|
*
|
||||||
|
* @author Mikko Tiihonen
|
||||||
|
*/
|
||||||
|
public class VisibleBufferedInputStream extends InputStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a direct read to byte array is called that would require a smaller read from the wrapped
|
||||||
|
* stream that MINIMUM_READ then first fill the buffer and serve the bytes from there. Larger
|
||||||
|
* reads are directly done to the provided byte array.
|
||||||
|
*/
|
||||||
|
private static final int MINIMUM_READ = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In how large spans is the C string zero-byte scanned.
|
||||||
|
*/
|
||||||
|
private static final int STRING_SCAN_SPAN = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wrapped input stream.
|
||||||
|
*/
|
||||||
|
private final InputStream wrapped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buffer.
|
||||||
|
*/
|
||||||
|
private byte[] buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current read position in the buffer.
|
||||||
|
*/
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How far is the buffer filled with valid data.
|
||||||
|
*/
|
||||||
|
private int endIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* socket timeout has been requested
|
||||||
|
*/
|
||||||
|
private boolean timeoutRequested;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new buffer around the given stream.
|
||||||
|
*
|
||||||
|
* @param in The stream to buffer.
|
||||||
|
* @param bufferSize The initial size of the buffer.
|
||||||
|
*/
|
||||||
|
public VisibleBufferedInputStream(InputStream in, int bufferSize) {
|
||||||
|
wrapped = in;
|
||||||
|
buffer = new byte[bufferSize < MINIMUM_READ ? MINIMUM_READ : bufferSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (ensureBytes(1)) {
|
||||||
|
return buffer[index++] & 0xFF;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a byte from the buffer without advancing the index pointer.
|
||||||
|
*
|
||||||
|
* @return byte from the buffer without advancing the index pointer
|
||||||
|
* @throws IOException if something wrong happens
|
||||||
|
*/
|
||||||
|
public int peek() throws IOException {
|
||||||
|
if (ensureBytes(1)) {
|
||||||
|
return buffer[index] & 0xFF;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads byte from the buffer without any checks. This method never reads from the underlaying
|
||||||
|
* stream. Before calling this method the {@link #ensureBytes} method must have been called.
|
||||||
|
*
|
||||||
|
* @return The next byte from the buffer.
|
||||||
|
* @throws ArrayIndexOutOfBoundsException If ensureBytes was not called to make sure the buffer
|
||||||
|
* contains the byte.
|
||||||
|
*/
|
||||||
|
public byte readRaw() {
|
||||||
|
return buffer[index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the buffer contains at least n bytes. This method invalidates the buffer and index
|
||||||
|
* fields.
|
||||||
|
*
|
||||||
|
* @param n The amount of bytes to ensure exists in buffer
|
||||||
|
* @return true if required bytes are available and false if EOF
|
||||||
|
* @throws IOException If reading of the wrapped stream failed.
|
||||||
|
*/
|
||||||
|
public boolean ensureBytes(int n) throws IOException {
|
||||||
|
return ensureBytes(n, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the buffer contains at least n bytes. This method invalidates the buffer and index
|
||||||
|
* fields.
|
||||||
|
*
|
||||||
|
* @param n The amount of bytes to ensure exists in buffer
|
||||||
|
* @param block whether or not to block the IO
|
||||||
|
* @return true if required bytes are available and false if EOF or the parameter block was false and socket timeout occurred.
|
||||||
|
* @throws IOException If reading of the wrapped stream failed.
|
||||||
|
*/
|
||||||
|
public boolean ensureBytes(int n, boolean block) throws IOException {
|
||||||
|
int required = n - endIndex + index;
|
||||||
|
while (required > 0) {
|
||||||
|
if (!readMore(required, block)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
required = n - endIndex + index;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads more bytes into the buffer.
|
||||||
|
*
|
||||||
|
* @param wanted How much should be at least read.
|
||||||
|
* @return True if at least some bytes were read.
|
||||||
|
* @throws IOException If reading of the wrapped stream failed.
|
||||||
|
*/
|
||||||
|
private boolean readMore(int wanted, boolean block) throws IOException {
|
||||||
|
if (endIndex == index) {
|
||||||
|
index = 0;
|
||||||
|
endIndex = 0;
|
||||||
|
}
|
||||||
|
int canFit = buffer.length - endIndex;
|
||||||
|
if (canFit < wanted) {
|
||||||
|
// would the wanted bytes fit if we compacted the buffer
|
||||||
|
// and still leave some slack
|
||||||
|
if (index + canFit > wanted + MINIMUM_READ) {
|
||||||
|
compact();
|
||||||
|
} else {
|
||||||
|
doubleBuffer();
|
||||||
|
}
|
||||||
|
canFit = buffer.length - endIndex;
|
||||||
|
}
|
||||||
|
int read = 0;
|
||||||
|
try {
|
||||||
|
read = wrapped.read(buffer, endIndex, canFit);
|
||||||
|
if (!block && read == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
if (!block) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (timeoutRequested) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (read < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
endIndex += read;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doubles the size of the buffer.
|
||||||
|
*/
|
||||||
|
private void doubleBuffer() {
|
||||||
|
byte[] buf = new byte[buffer.length * 2];
|
||||||
|
moveBufferTo(buf);
|
||||||
|
buffer = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compacts the unread bytes of the buffer to the beginning of the buffer.
|
||||||
|
*/
|
||||||
|
private void compact() {
|
||||||
|
moveBufferTo(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves bytes from the buffer to the beginning of the destination buffer. Also sets the index and
|
||||||
|
* endIndex variables.
|
||||||
|
*
|
||||||
|
* @param dest The destination buffer.
|
||||||
|
*/
|
||||||
|
private void moveBufferTo(byte[] dest) {
|
||||||
|
int size = endIndex - index;
|
||||||
|
System.arraycopy(buffer, index, dest, 0, size);
|
||||||
|
index = 0;
|
||||||
|
endIndex = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int read(byte[] to, int off, int len) throws IOException {
|
||||||
|
if ((off | len | (off + len) | (to.length - (off + len))) < 0) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
} else if (len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the read would go to wrapped stream, but would result
|
||||||
|
// in a small read then try read to the buffer instead
|
||||||
|
int avail = endIndex - index;
|
||||||
|
if (len - avail < MINIMUM_READ) {
|
||||||
|
ensureBytes(len);
|
||||||
|
avail = endIndex - index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first copy from buffer
|
||||||
|
if (avail > 0) {
|
||||||
|
if (len <= avail) {
|
||||||
|
System.arraycopy(buffer, index, to, off, len);
|
||||||
|
index += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
System.arraycopy(buffer, index, to, off, avail);
|
||||||
|
len -= avail;
|
||||||
|
off += avail;
|
||||||
|
}
|
||||||
|
int read = avail;
|
||||||
|
|
||||||
|
// good place to reset index because the buffer is fully drained
|
||||||
|
index = 0;
|
||||||
|
endIndex = 0;
|
||||||
|
|
||||||
|
// then directly from wrapped stream
|
||||||
|
do {
|
||||||
|
int r;
|
||||||
|
try {
|
||||||
|
r = wrapped.read(to, off, len);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
if (read == 0 && timeoutRequested) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
if (r <= 0) {
|
||||||
|
return read == 0 ? r : read;
|
||||||
|
}
|
||||||
|
read += r;
|
||||||
|
off += r;
|
||||||
|
len -= r;
|
||||||
|
} while (len > 0);
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
int avail = endIndex - index;
|
||||||
|
if (n >= Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalArgumentException("n is too large");
|
||||||
|
}
|
||||||
|
if (avail >= n) {
|
||||||
|
index = index + (int)n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
n -= avail;
|
||||||
|
index = 0;
|
||||||
|
endIndex = 0;
|
||||||
|
return avail + wrapped.skip(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int available() throws IOException {
|
||||||
|
int avail = endIndex - index;
|
||||||
|
return avail > 0 ? avail : wrapped.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
wrapped.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns direct handle to the used buffer. Use the {@link #ensureBytes} to prefill required
|
||||||
|
* bytes the buffer and {@link #getIndex} to fetch the current position of the buffer.
|
||||||
|
*
|
||||||
|
* @return The underlaying buffer.
|
||||||
|
*/
|
||||||
|
public byte[] getBuffer() {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current read position in the buffer.
|
||||||
|
*
|
||||||
|
* @return the current read position in the buffer.
|
||||||
|
*/
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the length of the next null terminated string (C-style string) from the stream.
|
||||||
|
*
|
||||||
|
* @return The length of the next null terminated string.
|
||||||
|
* @throws IOException If reading of stream fails.
|
||||||
|
* @throws EOFException If the stream did not contain any null terminators.
|
||||||
|
*/
|
||||||
|
public int scanCStringLength() throws IOException {
|
||||||
|
int pos = index;
|
||||||
|
while (true) {
|
||||||
|
while (pos < endIndex) {
|
||||||
|
if (buffer[pos++] == '\0') {
|
||||||
|
return pos - index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!readMore(STRING_SCAN_SPAN, true)) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
pos = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeoutRequested(boolean timeoutRequested) {
|
||||||
|
this.timeoutRequested = timeoutRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the wrapped stream
|
||||||
|
*/
|
||||||
|
public InputStream getWrapped() {
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.plugin.AuthenticationPlugin;
|
||||||
|
import org.postgresql.plugin.AuthenticationRequestType;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.ObjectFactory;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
class AuthenticationPluginManager {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AuthenticationPluginManager.class.getName());
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PasswordAction<T, R> {
|
||||||
|
R apply(T password) throws PSQLException, IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationPluginManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a password is requested by the server during connection initiation, this
|
||||||
|
* method will be invoked to supply the password. This method will only be
|
||||||
|
* invoked if the server actually requests a password, e.g. trust authentication
|
||||||
|
* will skip it entirely.
|
||||||
|
*
|
||||||
|
* <p>The caller provides a action method that will be invoked with the {@code char[]}
|
||||||
|
* password. After completion, for security reasons the {@code char[]} array will be
|
||||||
|
* wiped by filling it with zeroes. Callers must not rely on being able to read
|
||||||
|
* the password {@code char[]} after the action has completed.</p>
|
||||||
|
*
|
||||||
|
* @param type The authentication type that is being requested
|
||||||
|
* @param info The connection properties for the connection
|
||||||
|
* @param action The action to invoke with the password
|
||||||
|
* @throws PSQLException Throws a PSQLException if the plugin class cannot be instantiated
|
||||||
|
* @throws IOException Bubbles up any thrown IOException from the provided action
|
||||||
|
*/
|
||||||
|
public static <T> T withPassword(AuthenticationRequestType type, Properties info,
|
||||||
|
PasswordAction<char [], T> action) throws PSQLException, IOException {
|
||||||
|
char[] password = null;
|
||||||
|
|
||||||
|
String authPluginClassName = PGProperty.AUTHENTICATION_PLUGIN_CLASS_NAME.getOrDefault(info);
|
||||||
|
|
||||||
|
if (authPluginClassName == null || "".equals(authPluginClassName)) {
|
||||||
|
// Default auth plugin simply pulls password directly from connection properties
|
||||||
|
String passwordText = PGProperty.PASSWORD.getOrDefault(info);
|
||||||
|
if (passwordText != null) {
|
||||||
|
password = passwordText.toCharArray();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AuthenticationPlugin authPlugin;
|
||||||
|
try {
|
||||||
|
authPlugin = ObjectFactory.instantiate(AuthenticationPlugin.class, authPluginClassName, info,
|
||||||
|
false, null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
String msg = GT.tr("Unable to load Authentication Plugin {0}", authPluginClassName);
|
||||||
|
LOGGER.log(Level.FINE, msg, ex);
|
||||||
|
throw new PSQLException(msg, PSQLState.INVALID_PARAMETER_VALUE, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
password = authPlugin.getPassword(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return action.apply(password);
|
||||||
|
} finally {
|
||||||
|
if (password != null) {
|
||||||
|
Arrays.fill(password, (char) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper that wraps {@link #withPassword(AuthenticationRequestType, Properties, PasswordAction)}, checks that it is not-null, and encodes
|
||||||
|
* it as a byte array. Used by internal code paths that require an encoded password
|
||||||
|
* that may be an empty string, but not null.
|
||||||
|
*
|
||||||
|
* <p>The caller provides a callback method that will be invoked with the {@code byte[]}
|
||||||
|
* encoded password. After completion, for security reasons the {@code byte[]} array will be
|
||||||
|
* wiped by filling it with zeroes. Callers must not rely on being able to read
|
||||||
|
* the password {@code byte[]} after the callback has completed.</p>
|
||||||
|
|
||||||
|
* @param type The authentication type that is being requested
|
||||||
|
* @param info The connection properties for the connection
|
||||||
|
* @param action The action to invoke with the encoded password
|
||||||
|
* @throws PSQLException Throws a PSQLException if the plugin class cannot be instantiated or if the retrieved password is null.
|
||||||
|
* @throws IOException Bubbles up any thrown IOException from the provided callback
|
||||||
|
*/
|
||||||
|
public static <T> T withEncodedPassword(AuthenticationRequestType type, Properties info,
|
||||||
|
PasswordAction<byte[], T> action) throws PSQLException, IOException {
|
||||||
|
byte[] encodedPassword = withPassword(type, info, password -> {
|
||||||
|
if (password == null) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The server requested password-based authentication, but no password was provided by plugin {0}",
|
||||||
|
PGProperty.AUTHENTICATION_PLUGIN_CLASS_NAME.getOrDefault(info)),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(password));
|
||||||
|
byte[] bytes = new byte[buf.limit()];
|
||||||
|
buf.get(bytes);
|
||||||
|
return bytes;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return action.apply(encodedPassword);
|
||||||
|
} finally {
|
||||||
|
Arrays.fill(encodedPassword, (byte) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
pgjdbc/src/main/java/org/postgresql/core/v3/BatchedQuery.java
Normal file
182
pgjdbc/src/main/java/org/postgresql/core/v3/BatchedQuery.java
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.NativeQuery;
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purpose of this object is to support batched query re write behaviour. Responsibility for
|
||||||
|
* tracking the batch size and implement the clean up of the query fragments after the batch execute
|
||||||
|
* is complete. Intended to be used to wrap a Query that is present in the batchStatements
|
||||||
|
* collection.
|
||||||
|
*
|
||||||
|
* @author Jeremy Whiting jwhiting@redhat.com
|
||||||
|
* @author Christopher Deckers (chrriis@gmail.com)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class BatchedQuery extends SimpleQuery {
|
||||||
|
|
||||||
|
private String sql;
|
||||||
|
private final int valuesBraceOpenPosition;
|
||||||
|
private final int valuesBraceClosePosition;
|
||||||
|
private final int batchSize;
|
||||||
|
private BatchedQuery [] blocks;
|
||||||
|
|
||||||
|
public BatchedQuery(NativeQuery query, TypeTransferModeRegistry transferModeRegistry,
|
||||||
|
int valuesBraceOpenPosition,
|
||||||
|
int valuesBraceClosePosition, boolean sanitiserDisabled) {
|
||||||
|
super(query, transferModeRegistry, sanitiserDisabled);
|
||||||
|
this.valuesBraceOpenPosition = valuesBraceOpenPosition;
|
||||||
|
this.valuesBraceClosePosition = valuesBraceClosePosition;
|
||||||
|
this.batchSize = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BatchedQuery(BatchedQuery src, int batchSize) {
|
||||||
|
super(src);
|
||||||
|
this.valuesBraceOpenPosition = src.valuesBraceOpenPosition;
|
||||||
|
this.valuesBraceClosePosition = src.valuesBraceClosePosition;
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchedQuery deriveForMultiBatch(int valueBlock) {
|
||||||
|
if (getBatchSize() != 1) {
|
||||||
|
throw new IllegalStateException("Only the original decorator can be derived.");
|
||||||
|
}
|
||||||
|
if (valueBlock == 1) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
int index = Integer.numberOfTrailingZeros(valueBlock) - 1;
|
||||||
|
if (valueBlock > 128 || valueBlock != (1 << (index + 1))) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Expected value block should be a power of 2 smaller or equal to 128. Actual block is "
|
||||||
|
+ valueBlock);
|
||||||
|
}
|
||||||
|
if (blocks == null) {
|
||||||
|
blocks = new BatchedQuery[7];
|
||||||
|
}
|
||||||
|
BatchedQuery bq = blocks[index];
|
||||||
|
if (bq == null) {
|
||||||
|
bq = new BatchedQuery(this, valueBlock);
|
||||||
|
blocks[index] = bq;
|
||||||
|
}
|
||||||
|
return bq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBatchSize() {
|
||||||
|
return batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to return the sql based on number of batches. Skipping the initial
|
||||||
|
* batch.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getNativeSql() {
|
||||||
|
if (sql != null) {
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
sql = buildNativeSql(null);
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildNativeSql(ParameterList params) {
|
||||||
|
String sql = null;
|
||||||
|
// dynamically build sql with parameters for batches
|
||||||
|
String nativeSql = super.getNativeSql();
|
||||||
|
int batchSize = getBatchSize();
|
||||||
|
if (batchSize < 2) {
|
||||||
|
sql = nativeSql;
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
if (nativeSql == null) {
|
||||||
|
sql = "";
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
int valuesBlockCharCount = 0;
|
||||||
|
// Split the values section around every dynamic parameter.
|
||||||
|
int[] bindPositions = getNativeQuery().bindPositions;
|
||||||
|
int[] chunkStart = new int[1 + bindPositions.length];
|
||||||
|
int[] chunkEnd = new int[1 + bindPositions.length];
|
||||||
|
chunkStart[0] = valuesBraceOpenPosition;
|
||||||
|
if (bindPositions.length == 0) {
|
||||||
|
valuesBlockCharCount = valuesBraceClosePosition - valuesBraceOpenPosition + 1;
|
||||||
|
chunkEnd[0] = valuesBraceClosePosition + 1;
|
||||||
|
} else {
|
||||||
|
chunkEnd[0] = bindPositions[0];
|
||||||
|
// valuesBlockCharCount += chunks[0].length;
|
||||||
|
valuesBlockCharCount += chunkEnd[0] - chunkStart[0];
|
||||||
|
for (int i = 0; i < bindPositions.length; i++) {
|
||||||
|
int startIndex = bindPositions[i] + 2;
|
||||||
|
int endIndex =
|
||||||
|
i < bindPositions.length - 1 ? bindPositions[i + 1] : valuesBraceClosePosition + 1;
|
||||||
|
for (; startIndex < endIndex; startIndex++) {
|
||||||
|
if (!Character.isDigit(nativeSql.charAt(startIndex))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chunkStart[i + 1] = startIndex;
|
||||||
|
chunkEnd[i + 1] = endIndex;
|
||||||
|
// valuesBlockCharCount += chunks[i + 1].length;
|
||||||
|
valuesBlockCharCount += chunkEnd[i + 1] - chunkStart[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int length = nativeSql.length();
|
||||||
|
//valuesBraceOpenPosition + valuesBlockCharCount;
|
||||||
|
length += NativeQuery.calculateBindLength(bindPositions.length * batchSize);
|
||||||
|
length -= NativeQuery.calculateBindLength(bindPositions.length);
|
||||||
|
length += (valuesBlockCharCount + 1 /*comma*/) * (batchSize - 1 /* initial sql */);
|
||||||
|
|
||||||
|
StringBuilder s = new StringBuilder(length);
|
||||||
|
// Add query until end of values parameter block.
|
||||||
|
int pos;
|
||||||
|
if (bindPositions.length > 0 && params == null) {
|
||||||
|
// Add the first values (...) clause, it would be values($1,..., $n), and it matches with
|
||||||
|
// the values clause of a simple non-rewritten SQL
|
||||||
|
s.append(nativeSql, 0, valuesBraceClosePosition + 1);
|
||||||
|
pos = bindPositions.length + 1;
|
||||||
|
} else {
|
||||||
|
pos = 1;
|
||||||
|
batchSize++; // do not use super.toString(params) as it does not work if query ends with --
|
||||||
|
// We need to carefully add (...),(...), and we do not want to get (...) --, (...)
|
||||||
|
// s.append(super.toString(params));
|
||||||
|
s.append(nativeSql, 0, valuesBraceOpenPosition);
|
||||||
|
}
|
||||||
|
for (int i = 2; i <= batchSize; i++) {
|
||||||
|
if (i > 2 || pos != 1) {
|
||||||
|
// For "has binds" the first valuds
|
||||||
|
s.append(',');
|
||||||
|
}
|
||||||
|
s.append(nativeSql, chunkStart[0], chunkEnd[0]);
|
||||||
|
for (int j = 1; j < chunkStart.length; j++) {
|
||||||
|
if (params == null) {
|
||||||
|
NativeQuery.appendBindName(s, pos++);
|
||||||
|
} else {
|
||||||
|
s.append(params.toString(pos++, true));
|
||||||
|
}
|
||||||
|
s.append(nativeSql, chunkStart[j], chunkEnd[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add trailing content: final query is like original with multi values.
|
||||||
|
// This could contain "--" comments, so it is important to add them at end.
|
||||||
|
s.append(nativeSql, valuesBraceClosePosition + 1, nativeSql.length());
|
||||||
|
sql = s.toString();
|
||||||
|
// Predict length only when building sql with $1, $2, ... (that is no specific params given)
|
||||||
|
assert params != null || s.length() == length
|
||||||
|
: "Predicted length != actual: " + length + " !=" + s.length();
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(ParameterList params) {
|
||||||
|
if (getBatchSize() < 2) {
|
||||||
|
return super.toString(params);
|
||||||
|
}
|
||||||
|
return buildNativeSql(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter list for V3 query strings that contain multiple statements. We delegate to one
|
||||||
|
* SimpleParameterList per statement, and translate parameter indexes as needed.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
class CompositeParameterList implements V3ParameterList {
|
||||||
|
CompositeParameterList(SimpleParameterList[] subparams, int[] offsets) {
|
||||||
|
this.subparams = subparams;
|
||||||
|
this.offsets = offsets;
|
||||||
|
this.total = offsets[offsets.length - 1] + subparams[offsets.length - 1].getInParameterCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findSubParam(int index) throws SQLException {
|
||||||
|
if (index < 1 || index > total) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The column index is out of range: {0}, number of columns: {1}.", index, total),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = offsets.length - 1; i >= 0; i--) {
|
||||||
|
if (offsets[i] < index) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("I am confused; can't find a subparam for index " + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(int index, int sqlType) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDirection(int i) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getParameterCount() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInParameterCount() {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOutParameterCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getTypeOIDs() {
|
||||||
|
int[] oids = new int[total];
|
||||||
|
for (int i = 0; i < offsets.length; i++) {
|
||||||
|
int[] subOids = subparams[i].getTypeOIDs();
|
||||||
|
System.arraycopy(subOids, 0, oids, offsets[i], subOids.length);
|
||||||
|
}
|
||||||
|
return oids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIntParameter(int index, int value) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setIntParameter(index - offsets[sub], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLiteralParameter(int index, String value, int oid) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setStringParameter(index - offsets[sub], value, oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStringParameter(int index, String value, int oid) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setStringParameter(index - offsets[sub], value, oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryParameter(int index, byte[] value, int oid) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setBinaryParameter(index - offsets[sub], value, oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, byte[] data, int offset, int length) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setBytea(index - offsets[sub], data, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, InputStream stream, int length) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setBytea(index - offsets[sub], stream, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, InputStream stream) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setBytea(index - offsets[sub], stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, ByteStreamWriter writer) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setBytea(index - offsets[sub], writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setText(int index, InputStream stream) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setText(index - offsets[sub], stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int index, int oid) throws SQLException {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
subparams[sub].setNull(index - offsets[sub], oid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(int index, boolean standardConformingStrings) {
|
||||||
|
try {
|
||||||
|
int sub = findSubParam(index);
|
||||||
|
return subparams[sub].toString(index - offsets[sub], standardConformingStrings);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new IllegalStateException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterList copy() {
|
||||||
|
SimpleParameterList[] copySub = new SimpleParameterList[subparams.length];
|
||||||
|
for (int sub = 0; sub < subparams.length; sub++) {
|
||||||
|
copySub[sub] = (SimpleParameterList) subparams[sub].copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CompositeParameterList(copySub, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
for (SimpleParameterList subparam : subparams) {
|
||||||
|
subparam.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleParameterList [] getSubparams() {
|
||||||
|
return subparams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkAllParametersSet() throws SQLException {
|
||||||
|
for (SimpleParameterList subparam : subparams) {
|
||||||
|
subparam.checkAllParametersSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [][] getEncoding() {
|
||||||
|
return null; // unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] getFlags() {
|
||||||
|
return null; // unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int [] getParamTypes() {
|
||||||
|
return null; // unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object [] getValues() {
|
||||||
|
return null; // unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendAll(ParameterList list) throws SQLException {
|
||||||
|
// no-op, unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void convertFunctionOutParameters() {
|
||||||
|
for (SimpleParameterList subparam : subparams) {
|
||||||
|
subparam.convertFunctionOutParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int total;
|
||||||
|
private final SimpleParameterList[] subparams;
|
||||||
|
private final int[] offsets;
|
||||||
|
}
|
111
pgjdbc/src/main/java/org/postgresql/core/v3/CompositeQuery.java
Normal file
111
pgjdbc/src/main/java/org/postgresql/core/v3/CompositeQuery.java
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
import org.postgresql.core.Query;
|
||||||
|
import org.postgresql.core.SqlCommand;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V3 Query implementation for queries that involve multiple statements. We split it up into one
|
||||||
|
* SimpleQuery per statement, and wrap the corresponding per-statement SimpleParameterList objects
|
||||||
|
* in a CompositeParameterList.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
class CompositeQuery implements Query {
|
||||||
|
CompositeQuery(SimpleQuery[] subqueries, int[] offsets) {
|
||||||
|
this.subqueries = subqueries;
|
||||||
|
this.offsets = offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterList createParameterList() {
|
||||||
|
SimpleParameterList[] subparams = new SimpleParameterList[subqueries.length];
|
||||||
|
for (int i = 0; i < subqueries.length; i++) {
|
||||||
|
subparams[i] = (SimpleParameterList) subqueries[i].createParameterList();
|
||||||
|
}
|
||||||
|
return new CompositeParameterList(subparams, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(ParameterList parameters) {
|
||||||
|
StringBuilder sbuf = new StringBuilder(subqueries[0].toString());
|
||||||
|
for (int i = 1; i < subqueries.length; i++) {
|
||||||
|
sbuf.append(';');
|
||||||
|
sbuf.append(subqueries[i]);
|
||||||
|
}
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNativeSql() {
|
||||||
|
StringBuilder sbuf = new StringBuilder(subqueries[0].getNativeSql());
|
||||||
|
for (int i = 1; i < subqueries.length; i++) {
|
||||||
|
sbuf.append(';');
|
||||||
|
sbuf.append(subqueries[i].getNativeSql());
|
||||||
|
}
|
||||||
|
return sbuf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlCommand getSqlCommand() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toString(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
for (SimpleQuery subquery : subqueries) {
|
||||||
|
subquery.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query[] getSubqueries() {
|
||||||
|
return subqueries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStatementDescribed() {
|
||||||
|
for (SimpleQuery subquery : subqueries) {
|
||||||
|
if (!subquery.isStatementDescribed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (SimpleQuery subquery : subqueries) {
|
||||||
|
if (!subquery.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBatchSize() {
|
||||||
|
return 0; // no-op, unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> getResultSetColumnNameIndexMap() {
|
||||||
|
return null; // unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SimpleQuery[] subqueries;
|
||||||
|
private final int[] offsets;
|
||||||
|
}
|
|
@ -0,0 +1,907 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.PGProperty;
|
||||||
|
import org.postgresql.core.ConnectionFactory;
|
||||||
|
import org.postgresql.core.PGStream;
|
||||||
|
import org.postgresql.core.QueryExecutor;
|
||||||
|
import org.postgresql.core.ServerVersion;
|
||||||
|
import org.postgresql.core.SetupQueryRunner;
|
||||||
|
import org.postgresql.core.SocketFactoryFactory;
|
||||||
|
import org.postgresql.core.Tuple;
|
||||||
|
import org.postgresql.core.Utils;
|
||||||
|
import org.postgresql.core.Version;
|
||||||
|
import org.postgresql.gss.MakeGSS;
|
||||||
|
import org.postgresql.hostchooser.CandidateHost;
|
||||||
|
import org.postgresql.hostchooser.GlobalHostStatusTracker;
|
||||||
|
import org.postgresql.hostchooser.HostChooser;
|
||||||
|
import org.postgresql.hostchooser.HostChooserFactory;
|
||||||
|
import org.postgresql.hostchooser.HostRequirement;
|
||||||
|
import org.postgresql.hostchooser.HostStatus;
|
||||||
|
import org.postgresql.jdbc.GSSEncMode;
|
||||||
|
import org.postgresql.jdbc.SslMode;
|
||||||
|
import org.postgresql.plugin.AuthenticationRequestType;
|
||||||
|
import org.postgresql.scram.ScramAuthenticator;
|
||||||
|
import org.postgresql.ssl.MakeSSL;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.HostSpec;
|
||||||
|
import org.postgresql.util.MD5Digest;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
import org.postgresql.util.ServerErrorMessage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectionFactory implementation for version 3 (7.4+) connections.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com), based on the previous implementation
|
||||||
|
*/
|
||||||
|
public class ConnectionFactoryImpl extends ConnectionFactory {
|
||||||
|
|
||||||
|
private static class StartupParam {
|
||||||
|
private final String key;
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
StartupParam(String key, String value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.key + "=" + this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncodedKey() {
|
||||||
|
return this.key.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncodedValue() {
|
||||||
|
return this.value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ConnectionFactoryImpl.class.getName());
|
||||||
|
private static final int AUTH_REQ_OK = 0;
|
||||||
|
private static final int AUTH_REQ_KRB4 = 1;
|
||||||
|
private static final int AUTH_REQ_KRB5 = 2;
|
||||||
|
private static final int AUTH_REQ_PASSWORD = 3;
|
||||||
|
private static final int AUTH_REQ_CRYPT = 4;
|
||||||
|
private static final int AUTH_REQ_MD5 = 5;
|
||||||
|
private static final int AUTH_REQ_SCM = 6;
|
||||||
|
private static final int AUTH_REQ_GSS = 7;
|
||||||
|
private static final int AUTH_REQ_GSS_CONTINUE = 8;
|
||||||
|
private static final int AUTH_REQ_SSPI = 9;
|
||||||
|
private static final int AUTH_REQ_SASL = 10;
|
||||||
|
private static final int AUTH_REQ_SASL_CONTINUE = 11;
|
||||||
|
private static final int AUTH_REQ_SASL_FINAL = 12;
|
||||||
|
|
||||||
|
private static final String IN_HOT_STANDBY = "in_hot_standby";
|
||||||
|
|
||||||
|
public ConnectionFactoryImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGStream tryConnect(Properties info, SocketFactory socketFactory, HostSpec hostSpec,
|
||||||
|
SslMode sslMode, GSSEncMode gssEncMode)
|
||||||
|
throws SQLException, IOException {
|
||||||
|
int connectTimeout = PGProperty.CONNECT_TIMEOUT.getInt(info) * 1000;
|
||||||
|
String user = PGProperty.USER.getOrDefault(info);
|
||||||
|
String database = PGProperty.PG_DBNAME.getOrDefault(info);
|
||||||
|
if (user == null) {
|
||||||
|
throw new PSQLException(GT.tr("User cannot be null"), PSQLState.INVALID_NAME);
|
||||||
|
}
|
||||||
|
if (database == null) {
|
||||||
|
throw new PSQLException(GT.tr("Database cannot be null"), PSQLState.INVALID_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGStream newStream = new PGStream(socketFactory, hostSpec, connectTimeout);
|
||||||
|
try {
|
||||||
|
// Set the socket timeout if the "socketTimeout" property has been set.
|
||||||
|
int socketTimeout = PGProperty.SOCKET_TIMEOUT.getInt(info);
|
||||||
|
if (socketTimeout > 0) {
|
||||||
|
newStream.setNetworkTimeout(socketTimeout * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
String maxResultBuffer = PGProperty.MAX_RESULT_BUFFER.getOrDefault(info);
|
||||||
|
newStream.setMaxResultBuffer(maxResultBuffer);
|
||||||
|
|
||||||
|
// Enable TCP keep-alive probe if required.
|
||||||
|
boolean requireTCPKeepAlive = PGProperty.TCP_KEEP_ALIVE.getBoolean(info);
|
||||||
|
newStream.getSocket().setKeepAlive(requireTCPKeepAlive);
|
||||||
|
|
||||||
|
// Enable TCP no delay if required
|
||||||
|
boolean requireTCPNoDelay = PGProperty.TCP_NO_DELAY.getBoolean(info);
|
||||||
|
newStream.getSocket().setTcpNoDelay(requireTCPNoDelay);
|
||||||
|
|
||||||
|
// Try to set SO_SNDBUF and SO_RECVBUF socket options, if requested.
|
||||||
|
// If receiveBufferSize and send_buffer_size are set to a value greater
|
||||||
|
// than 0, adjust. -1 means use the system default, 0 is ignored since not
|
||||||
|
// supported.
|
||||||
|
|
||||||
|
// Set SO_RECVBUF read buffer size
|
||||||
|
int receiveBufferSize = PGProperty.RECEIVE_BUFFER_SIZE.getInt(info);
|
||||||
|
if (receiveBufferSize > -1) {
|
||||||
|
// value of 0 not a valid buffer size value
|
||||||
|
if (receiveBufferSize > 0) {
|
||||||
|
newStream.getSocket().setReceiveBufferSize(receiveBufferSize);
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.WARNING, "Ignore invalid value for receiveBufferSize: {0}",
|
||||||
|
receiveBufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set SO_SNDBUF write buffer size
|
||||||
|
int sendBufferSize = PGProperty.SEND_BUFFER_SIZE.getInt(info);
|
||||||
|
if (sendBufferSize > -1) {
|
||||||
|
if (sendBufferSize > 0) {
|
||||||
|
newStream.getSocket().setSendBufferSize(sendBufferSize);
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.WARNING, "Ignore invalid value for sendBufferSize: {0}", sendBufferSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOGGER.isLoggable(Level.FINE)) {
|
||||||
|
LOGGER.log(Level.FINE, "Receive Buffer Size is {0}",
|
||||||
|
newStream.getSocket().getReceiveBufferSize());
|
||||||
|
LOGGER.log(Level.FINE, "Send Buffer Size is {0}",
|
||||||
|
newStream.getSocket().getSendBufferSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
newStream = enableGSSEncrypted(newStream, gssEncMode, hostSpec.getHost(), info, connectTimeout);
|
||||||
|
|
||||||
|
// if we have a security context then gss negotiation succeeded. Do not attempt SSL
|
||||||
|
// negotiation
|
||||||
|
if (!newStream.isGssEncrypted()) {
|
||||||
|
// Construct and send an ssl startup packet if requested.
|
||||||
|
newStream = enableSSL(newStream, sslMode, info, connectTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to set network timeout again, in case the stream changed due to GSS or SSL
|
||||||
|
if (socketTimeout > 0) {
|
||||||
|
newStream.setNetworkTimeout(socketTimeout * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<StartupParam> paramList = getParametersForStartup(user, database, info);
|
||||||
|
sendStartupPacket(newStream, paramList);
|
||||||
|
|
||||||
|
// Do authentication (until AuthenticationOk).
|
||||||
|
doAuthentication(newStream, hostSpec.getHost(), user, info);
|
||||||
|
|
||||||
|
return newStream;
|
||||||
|
} catch (Exception e) {
|
||||||
|
closeStream(newStream);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, Properties info) throws SQLException {
|
||||||
|
SslMode sslMode = SslMode.of(info);
|
||||||
|
GSSEncMode gssEncMode = GSSEncMode.of(info);
|
||||||
|
|
||||||
|
HostRequirement targetServerType;
|
||||||
|
String targetServerTypeStr = PGProperty.TARGET_SERVER_TYPE.getOrDefault(info);
|
||||||
|
try {
|
||||||
|
targetServerType = HostRequirement.getTargetServerType(targetServerTypeStr);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("Invalid targetServerType value: {0}", targetServerTypeStr),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketFactory socketFactory = SocketFactoryFactory.getSocketFactory(info);
|
||||||
|
|
||||||
|
HostChooser hostChooser =
|
||||||
|
HostChooserFactory.createHostChooser(hostSpecs, targetServerType, info);
|
||||||
|
Iterator<CandidateHost> hostIter = hostChooser.iterator();
|
||||||
|
Map<HostSpec, HostStatus> knownStates = new HashMap<>();
|
||||||
|
while (hostIter.hasNext()) {
|
||||||
|
CandidateHost candidateHost = hostIter.next();
|
||||||
|
HostSpec hostSpec = candidateHost.hostSpec;
|
||||||
|
LOGGER.log(Level.FINE, "Trying to establish a protocol version 3 connection to {0}", hostSpec);
|
||||||
|
|
||||||
|
// Note: per-connect-attempt status map is used here instead of GlobalHostStatusTracker
|
||||||
|
// for the case when "no good hosts" match (e.g. all the hosts are known as "connectfail")
|
||||||
|
// In that case, the system tries to connect to each host in order, thus it should not look into
|
||||||
|
// GlobalHostStatusTracker
|
||||||
|
HostStatus knownStatus = knownStates.get(hostSpec);
|
||||||
|
if (knownStatus != null && !candidateHost.targetServerType.allowConnectingTo(knownStatus)) {
|
||||||
|
if (LOGGER.isLoggable(Level.FINER)) {
|
||||||
|
LOGGER.log(Level.FINER, "Known status of host {0} is {1}, and required status was {2}. Will try next host",
|
||||||
|
new Object[]{hostSpec, knownStatus, candidateHost.targetServerType});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Establish a connection.
|
||||||
|
//
|
||||||
|
|
||||||
|
PGStream newStream = null;
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
newStream = tryConnect(info, socketFactory, hostSpec, sslMode, gssEncMode);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (sslMode == SslMode.PREFER
|
||||||
|
&& PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(e.getSQLState())) {
|
||||||
|
// Try non-SSL connection to cover case like "non-ssl only db"
|
||||||
|
// Note: PREFER allows loss of encryption, so no significant harm is made
|
||||||
|
Throwable ex = null;
|
||||||
|
try {
|
||||||
|
newStream =
|
||||||
|
tryConnect(info, socketFactory, hostSpec, SslMode.DISABLE, gssEncMode);
|
||||||
|
LOGGER.log(Level.FINE, "Downgraded to non-encrypted connection for host {0}",
|
||||||
|
hostSpec);
|
||||||
|
} catch (SQLException | IOException ee) {
|
||||||
|
ex = ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ex != null) {
|
||||||
|
log(Level.FINE, "sslMode==PREFER, however non-SSL connection failed as well", ex);
|
||||||
|
// non-SSL failed as well, so re-throw original exception
|
||||||
|
// Add non-SSL exception as suppressed
|
||||||
|
e.addSuppressed(ex);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else if (sslMode == SslMode.ALLOW
|
||||||
|
&& PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(e.getSQLState())) {
|
||||||
|
// Try using SSL
|
||||||
|
Throwable ex = null;
|
||||||
|
try {
|
||||||
|
newStream =
|
||||||
|
tryConnect(info, socketFactory, hostSpec, SslMode.REQUIRE, gssEncMode);
|
||||||
|
LOGGER.log(Level.FINE, "Upgraded to encrypted connection for host {0}",
|
||||||
|
hostSpec);
|
||||||
|
} catch (SQLException ee) {
|
||||||
|
ex = ee;
|
||||||
|
} catch (IOException ee) {
|
||||||
|
ex = ee; // Can't use multi-catch in Java 6 :(
|
||||||
|
}
|
||||||
|
if (ex != null) {
|
||||||
|
log(Level.FINE, "sslMode==ALLOW, however SSL connection failed as well", ex);
|
||||||
|
// non-SSL failed as well, so re-throw original exception
|
||||||
|
// Add SSL exception as suppressed
|
||||||
|
e.addSuppressed(ex);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cancelSignalTimeout = PGProperty.CANCEL_SIGNAL_TIMEOUT.getInt(info) * 1000;
|
||||||
|
|
||||||
|
// Do final startup.
|
||||||
|
QueryExecutor queryExecutor = new QueryExecutorImpl(newStream, cancelSignalTimeout, info);
|
||||||
|
|
||||||
|
// Check Primary or Secondary
|
||||||
|
HostStatus hostStatus = HostStatus.ConnectOK;
|
||||||
|
if (candidateHost.targetServerType != HostRequirement.any) {
|
||||||
|
hostStatus = isPrimary(queryExecutor) ? HostStatus.Primary : HostStatus.Secondary;
|
||||||
|
}
|
||||||
|
GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus);
|
||||||
|
knownStates.put(hostSpec, hostStatus);
|
||||||
|
if (!candidateHost.targetServerType.allowConnectingTo(hostStatus)) {
|
||||||
|
queryExecutor.close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
runInitialQueries(queryExecutor, info);
|
||||||
|
|
||||||
|
// And we're done.
|
||||||
|
return queryExecutor;
|
||||||
|
} catch (ConnectException cex) {
|
||||||
|
// Added by Peter Mount <peter@retep.org.uk>
|
||||||
|
// ConnectException is thrown when the connection cannot be made.
|
||||||
|
// we trap this an return a more meaningful message for the end user
|
||||||
|
GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
|
||||||
|
knownStates.put(hostSpec, HostStatus.ConnectFail);
|
||||||
|
if (hostIter.hasNext()) {
|
||||||
|
log(Level.FINE, "ConnectException occurred while connecting to {0}", cex, hostSpec);
|
||||||
|
// still more addresses to try
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new PSQLException(GT.tr(
|
||||||
|
"Connection to {0} refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.",
|
||||||
|
hostSpec), PSQLState.CONNECTION_UNABLE_TO_CONNECT, cex);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
closeStream(newStream);
|
||||||
|
GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
|
||||||
|
knownStates.put(hostSpec, HostStatus.ConnectFail);
|
||||||
|
if (hostIter.hasNext()) {
|
||||||
|
log(Level.FINE, "IOException occurred while connecting to {0}", ioe, hostSpec);
|
||||||
|
// still more addresses to try
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new PSQLException(GT.tr("The connection attempt failed."),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe);
|
||||||
|
} catch (SQLException se) {
|
||||||
|
closeStream(newStream);
|
||||||
|
GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail);
|
||||||
|
knownStates.put(hostSpec, HostStatus.ConnectFail);
|
||||||
|
if (hostIter.hasNext()) {
|
||||||
|
log(Level.FINE, "SQLException occurred while connecting to {0}", se, hostSpec);
|
||||||
|
// still more addresses to try
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw se;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new PSQLException(GT
|
||||||
|
.tr("Could not find a server with specified targetServerType: {0}", targetServerType),
|
||||||
|
PSQLState.CONNECTION_UNABLE_TO_CONNECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StartupParam> getParametersForStartup(String user, String database, Properties info) {
|
||||||
|
List<StartupParam> paramList = new ArrayList<>();
|
||||||
|
paramList.add(new StartupParam("user", user));
|
||||||
|
paramList.add(new StartupParam("database", database));
|
||||||
|
paramList.add(new StartupParam("client_encoding", "UTF8"));
|
||||||
|
paramList.add(new StartupParam("DateStyle", "ISO"));
|
||||||
|
paramList.add(new StartupParam("TimeZone", createPostgresTimeZone()));
|
||||||
|
|
||||||
|
Version assumeVersion = ServerVersion.from(PGProperty.ASSUME_MIN_SERVER_VERSION.getOrDefault(info));
|
||||||
|
|
||||||
|
if (assumeVersion.getVersionNum() >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
// User is explicitly telling us this is a 9.0+ server so set properties here:
|
||||||
|
paramList.add(new StartupParam("extra_float_digits", "3"));
|
||||||
|
String appName = PGProperty.APPLICATION_NAME.getOrDefault(info);
|
||||||
|
if (appName != null) {
|
||||||
|
paramList.add(new StartupParam("application_name", appName));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User has not explicitly told us that this is a 9.0+ server so stick to old default:
|
||||||
|
paramList.add(new StartupParam("extra_float_digits", "2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String replication = PGProperty.REPLICATION.getOrDefault(info);
|
||||||
|
if (replication != null && assumeVersion.getVersionNum() >= ServerVersion.v9_4.getVersionNum()) {
|
||||||
|
paramList.add(new StartupParam("replication", replication));
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentSchema = PGProperty.CURRENT_SCHEMA.getOrDefault(info);
|
||||||
|
if (currentSchema != null) {
|
||||||
|
paramList.add(new StartupParam("search_path", currentSchema));
|
||||||
|
}
|
||||||
|
|
||||||
|
String options = PGProperty.OPTIONS.getOrDefault(info);
|
||||||
|
if (options != null) {
|
||||||
|
paramList.add(new StartupParam("options", options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(Level level, String msg, Throwable thrown, Object... params) {
|
||||||
|
if (!LOGGER.isLoggable(level)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogRecord rec = new LogRecord(level, msg);
|
||||||
|
// Set the loggerName of the LogRecord with the current logger
|
||||||
|
rec.setLoggerName(LOGGER.getName());
|
||||||
|
rec.setParameters(params);
|
||||||
|
rec.setThrown(thrown);
|
||||||
|
LOGGER.log(rec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Java time zone to postgres time zone. All others stay the same except that GMT+nn
|
||||||
|
* changes to GMT-nn and vise versa.
|
||||||
|
* If you provide GMT+/-nn postgres uses POSIX rules which has a positive sign for west of Greenwich
|
||||||
|
* JAVA uses ISO rules which the positive sign is east of Greenwich
|
||||||
|
* To make matters more interesting postgres will always report in ISO
|
||||||
|
*
|
||||||
|
* @return The current JVM time zone in postgresql format.
|
||||||
|
*/
|
||||||
|
private static String createPostgresTimeZone() {
|
||||||
|
String tz = TimeZone.getDefault().getID();
|
||||||
|
if (tz.length() <= 3 || !tz.startsWith("GMT")) {
|
||||||
|
return tz;
|
||||||
|
}
|
||||||
|
char sign = tz.charAt(3);
|
||||||
|
String start;
|
||||||
|
switch (sign) {
|
||||||
|
case '+':
|
||||||
|
start = "GMT-";
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
start = "GMT+";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// unknown type
|
||||||
|
return tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start + tz.substring(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("fallthrough")
|
||||||
|
private PGStream enableGSSEncrypted(PGStream pgStream, GSSEncMode gssEncMode, String host, Properties info,
|
||||||
|
int connectTimeout)
|
||||||
|
throws IOException, PSQLException {
|
||||||
|
|
||||||
|
if ( gssEncMode == GSSEncMode.DISABLE ) {
|
||||||
|
return pgStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gssEncMode == GSSEncMode.ALLOW ) {
|
||||||
|
// start with plain text and let the server request it
|
||||||
|
return pgStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
at this point gssEncMode is either PREFER or REQUIRE
|
||||||
|
libpq looks to see if there is a ticket in the cache before asking
|
||||||
|
the server if it supports encrypted GSS connections or not.
|
||||||
|
since the user has specifically asked or either prefer or require we can
|
||||||
|
assume they want it.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
let's see if the server will allow a GSS encrypted connection
|
||||||
|
*/
|
||||||
|
String user = PGProperty.USER.getOrDefault(info);
|
||||||
|
if (user == null) {
|
||||||
|
throw new PSQLException("GSSAPI encryption required but was impossible user is null", PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to acquire a GSS encrypted connection
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> GSSENCRequest");
|
||||||
|
|
||||||
|
int gssTimeout = PGProperty.SSL_RESPONSE_TIMEOUT.getInt(info);
|
||||||
|
int currentTimeout = pgStream.getNetworkTimeout();
|
||||||
|
|
||||||
|
// if the current timeout is less than sslTimeout then
|
||||||
|
// use the smaller timeout. We could do something tricky
|
||||||
|
// here to not set it in that case but this is pretty readable
|
||||||
|
if (currentTimeout > 0 && currentTimeout < gssTimeout) {
|
||||||
|
gssTimeout = currentTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStream.setNetworkTimeout(gssTimeout);
|
||||||
|
|
||||||
|
// Send GSSEncryption request packet
|
||||||
|
pgStream.sendInteger4(8);
|
||||||
|
pgStream.sendInteger2(1234);
|
||||||
|
pgStream.sendInteger2(5680);
|
||||||
|
pgStream.flush();
|
||||||
|
// Now get the response from the backend, one of N, E, S.
|
||||||
|
int beresp = pgStream.receiveChar();
|
||||||
|
pgStream.setNetworkTimeout(currentTimeout);
|
||||||
|
switch (beresp) {
|
||||||
|
case 'E':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE GSSEncrypted Error");
|
||||||
|
|
||||||
|
// Server doesn't even know about the SSL handshake protocol
|
||||||
|
if (gssEncMode.requireEncryption()) {
|
||||||
|
throw new PSQLException(GT.tr("The server does not support GSS Encoding."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to reconnect to continue.
|
||||||
|
pgStream.close();
|
||||||
|
return new PGStream(pgStream.getSocketFactory(), pgStream.getHostSpec(), connectTimeout);
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE GSSEncrypted Refused");
|
||||||
|
|
||||||
|
// Server does not support gss encryption
|
||||||
|
if (gssEncMode.requireEncryption()) {
|
||||||
|
throw new PSQLException(GT.tr("The server does not support GSS Encryption."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgStream;
|
||||||
|
|
||||||
|
case 'G':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE GSSEncryptedOk");
|
||||||
|
try {
|
||||||
|
AuthenticationPluginManager.withPassword(AuthenticationRequestType.GSS, info, password -> {
|
||||||
|
MakeGSS.authenticate(true, pgStream, host, user, password,
|
||||||
|
PGProperty.JAAS_APPLICATION_NAME.getOrDefault(info),
|
||||||
|
PGProperty.KERBEROS_SERVER_NAME.getOrDefault(info), false, // TODO: fix this
|
||||||
|
PGProperty.JAAS_LOGIN.getBoolean(info),
|
||||||
|
PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
|
||||||
|
return void.class;
|
||||||
|
});
|
||||||
|
return pgStream;
|
||||||
|
} catch (PSQLException ex) {
|
||||||
|
// allow the connection to proceed
|
||||||
|
if (gssEncMode == GSSEncMode.PREFER) {
|
||||||
|
// we have to reconnect to continue
|
||||||
|
return new PGStream(pgStream, connectTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new PSQLException(GT.tr("An error occurred while setting up the GSS Encoded connection."),
|
||||||
|
PSQLState.PROTOCOL_VIOLATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGStream enableSSL(PGStream pgStream, SslMode sslMode, Properties info,
|
||||||
|
int connectTimeout)
|
||||||
|
throws IOException, PSQLException {
|
||||||
|
if (sslMode == SslMode.DISABLE) {
|
||||||
|
return pgStream;
|
||||||
|
}
|
||||||
|
if (sslMode == SslMode.ALLOW) {
|
||||||
|
// Allow ==> start with plaintext, use encryption if required by server
|
||||||
|
return pgStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> SSLRequest");
|
||||||
|
|
||||||
|
int sslTimeout = PGProperty.SSL_RESPONSE_TIMEOUT.getInt(info);
|
||||||
|
int currentTimeout = pgStream.getNetworkTimeout();
|
||||||
|
|
||||||
|
// if the current timeout is less than sslTimeout then
|
||||||
|
// use the smaller timeout. We could do something tricky
|
||||||
|
// here to not set it in that case but this is pretty readable
|
||||||
|
if (currentTimeout > 0 && currentTimeout < sslTimeout) {
|
||||||
|
sslTimeout = currentTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStream.setNetworkTimeout(sslTimeout);
|
||||||
|
// Send SSL request packet
|
||||||
|
pgStream.sendInteger4(8);
|
||||||
|
pgStream.sendInteger2(1234);
|
||||||
|
pgStream.sendInteger2(5679);
|
||||||
|
pgStream.flush();
|
||||||
|
|
||||||
|
// Now get the response from the backend, one of N, E, S.
|
||||||
|
int beresp = pgStream.receiveChar();
|
||||||
|
pgStream.setNetworkTimeout(currentTimeout);
|
||||||
|
|
||||||
|
switch (beresp) {
|
||||||
|
case 'E':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE SSLError");
|
||||||
|
|
||||||
|
// Server doesn't even know about the SSL handshake protocol
|
||||||
|
if (sslMode.requireEncryption()) {
|
||||||
|
throw new PSQLException(GT.tr("The server does not support SSL."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to reconnect to continue.
|
||||||
|
return new PGStream(pgStream, connectTimeout);
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE SSLRefused");
|
||||||
|
|
||||||
|
// Server does not support ssl
|
||||||
|
if (sslMode.requireEncryption()) {
|
||||||
|
throw new PSQLException(GT.tr("The server does not support SSL."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgStream;
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE SSLOk");
|
||||||
|
|
||||||
|
// Server supports ssl
|
||||||
|
MakeSSL.convert(pgStream, info);
|
||||||
|
return pgStream;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new PSQLException(GT.tr("An error occurred while setting up the SSL connection."),
|
||||||
|
PSQLState.PROTOCOL_VIOLATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendStartupPacket(PGStream pgStream, List<StartupParam> params)
|
||||||
|
throws IOException {
|
||||||
|
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||||
|
StringBuilder details = new StringBuilder();
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
details.append(", ");
|
||||||
|
}
|
||||||
|
details.append(params.get(i).toString());
|
||||||
|
}
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> StartupPacket({0})", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precalculate message length and encode params.
|
||||||
|
int length = 4 + 4;
|
||||||
|
byte[][] encodedParams = new byte[params.size() * 2][];
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
encodedParams[i * 2] = params.get(i).getEncodedKey();
|
||||||
|
encodedParams[i * 2 + 1] = params.get(i).getEncodedValue();
|
||||||
|
length += encodedParams[i * 2].length + 1 + encodedParams[i * 2 + 1].length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
length += 1; // Terminating \0
|
||||||
|
|
||||||
|
// Send the startup message.
|
||||||
|
pgStream.sendInteger4(length);
|
||||||
|
pgStream.sendInteger2(3); // protocol major
|
||||||
|
pgStream.sendInteger2(0); // protocol minor
|
||||||
|
for (byte[] encodedParam : encodedParams) {
|
||||||
|
pgStream.send(encodedParam);
|
||||||
|
pgStream.sendChar(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStream.sendChar(0);
|
||||||
|
pgStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doAuthentication(PGStream pgStream, String host, String user, Properties info) throws IOException, SQLException {
|
||||||
|
// Now get the response from the backend, either an error message
|
||||||
|
// or an authentication request
|
||||||
|
|
||||||
|
/* SCRAM authentication state, if used */
|
||||||
|
ScramAuthenticator scramAuthenticator = null;
|
||||||
|
|
||||||
|
authloop:
|
||||||
|
while (true) {
|
||||||
|
int beresp = pgStream.receiveChar();
|
||||||
|
|
||||||
|
switch (beresp) {
|
||||||
|
case 'E':
|
||||||
|
// An error occurred, so pass the error message to the
|
||||||
|
// user.
|
||||||
|
//
|
||||||
|
// The most common one to be thrown here is:
|
||||||
|
// "User authentication failed"
|
||||||
|
//
|
||||||
|
int elen = pgStream.receiveInteger4();
|
||||||
|
|
||||||
|
ServerErrorMessage errorMsg =
|
||||||
|
new ServerErrorMessage(pgStream.receiveErrorString(elen - 4));
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE ErrorMessage({0})", errorMsg);
|
||||||
|
throw new PSQLException(errorMsg, PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
|
||||||
|
|
||||||
|
case 'R':
|
||||||
|
// Authentication request.
|
||||||
|
// Get the message length
|
||||||
|
int msgLen = pgStream.receiveInteger4();
|
||||||
|
|
||||||
|
// Get the type of request
|
||||||
|
int areq = pgStream.receiveInteger4();
|
||||||
|
|
||||||
|
// Process the request.
|
||||||
|
switch (areq) {
|
||||||
|
case AUTH_REQ_MD5: {
|
||||||
|
byte[] md5Salt = pgStream.receive(4);
|
||||||
|
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE AuthenticationReqMD5(salt={0})", Utils.toHexString(md5Salt));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] digest = AuthenticationPluginManager.withEncodedPassword(
|
||||||
|
AuthenticationRequestType.MD5_PASSWORD, info,
|
||||||
|
encodedPassword -> MD5Digest.encode(user.getBytes(StandardCharsets.UTF_8),
|
||||||
|
encodedPassword, md5Salt)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> Password(md5digest={0})", new String(digest, StandardCharsets.US_ASCII));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pgStream.sendChar('p');
|
||||||
|
pgStream.sendInteger4(4 + digest.length + 1);
|
||||||
|
pgStream.send(digest);
|
||||||
|
} finally {
|
||||||
|
Arrays.fill(digest, (byte) 0);
|
||||||
|
}
|
||||||
|
pgStream.sendChar(0);
|
||||||
|
pgStream.flush();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AUTH_REQ_PASSWORD: {
|
||||||
|
LOGGER.log(Level.FINEST, "<=BE AuthenticationReqPassword");
|
||||||
|
LOGGER.log(Level.FINEST, " FE=> Password(password=<not shown>)");
|
||||||
|
|
||||||
|
AuthenticationPluginManager.withEncodedPassword(AuthenticationRequestType.CLEARTEXT_PASSWORD, info, encodedPassword -> {
|
||||||
|
pgStream.sendChar('p');
|
||||||
|
pgStream.sendInteger4(4 + encodedPassword.length + 1);
|
||||||
|
pgStream.send(encodedPassword);
|
||||||
|
return void.class;
|
||||||
|
});
|
||||||
|
pgStream.sendChar(0);
|
||||||
|
pgStream.flush();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AUTH_REQ_GSS:
|
||||||
|
/*
|
||||||
|
* Use GSSAPI if requested on all platforms, via JSSE.
|
||||||
|
*
|
||||||
|
* Note that this is slightly different to libpq, which uses SSPI for GSSAPI where
|
||||||
|
* supported. We prefer to use the existing Java JSSE Kerberos support rather than
|
||||||
|
* going to native (via JNA) calls where possible, so that JSSE system properties
|
||||||
|
* etc continue to work normally.
|
||||||
|
*
|
||||||
|
* Note that while SSPI is often Kerberos-based there's no guarantee it will be; it
|
||||||
|
* may be NTLM or anything else. If the client responds to an SSPI request via
|
||||||
|
* GSSAPI and the other end isn't using Kerberos for SSPI then authentication will
|
||||||
|
* fail.
|
||||||
|
*/
|
||||||
|
final String gsslib = PGProperty.GSS_LIB.getOrDefault(info);
|
||||||
|
final boolean usespnego = PGProperty.USE_SPNEGO.getBoolean(info);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use gssapi. If the user has specified a Kerberos server
|
||||||
|
* name we'll always use JSSE GSSAPI.
|
||||||
|
*/
|
||||||
|
if ("gssapi".equals(gsslib)) {
|
||||||
|
LOGGER.log(Level.FINE, "Using JSSE GSSAPI, param gsslib=gssapi");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use JGSS's GSSAPI for this request */
|
||||||
|
AuthenticationPluginManager.withPassword(AuthenticationRequestType.GSS, info, password -> {
|
||||||
|
MakeGSS.authenticate(false, pgStream, host, user, password,
|
||||||
|
PGProperty.JAAS_APPLICATION_NAME.getOrDefault(info),
|
||||||
|
PGProperty.KERBEROS_SERVER_NAME.getOrDefault(info), usespnego,
|
||||||
|
PGProperty.JAAS_LOGIN.getBoolean(info),
|
||||||
|
PGProperty.LOG_SERVER_ERROR_DETAIL.getBoolean(info));
|
||||||
|
return void.class;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUTH_REQ_GSS_CONTINUE:
|
||||||
|
// unused
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUTH_REQ_SASL:
|
||||||
|
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE AuthenticationSASL");
|
||||||
|
|
||||||
|
scramAuthenticator = AuthenticationPluginManager.withPassword(AuthenticationRequestType.SASL, info, password -> {
|
||||||
|
if (password == null) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr(
|
||||||
|
"The server requested SCRAM-based authentication, but no password was provided."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
if (password.length == 0) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr(
|
||||||
|
"The server requested SCRAM-based authentication, but the password is an empty string."),
|
||||||
|
PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
return new ScramAuthenticator(user, String.valueOf(password), pgStream);
|
||||||
|
});
|
||||||
|
scramAuthenticator.processServerMechanismsAndInit();
|
||||||
|
scramAuthenticator.sendScramClientFirstMessage();
|
||||||
|
// This works as follows:
|
||||||
|
// 1. When tests is run from IDE, it is assumed SCRAM library is on the classpath
|
||||||
|
// 2. In regular build for Java < 8 this `if` is deactivated and the code always throws
|
||||||
|
if (false) {
|
||||||
|
throw new PSQLException(GT.tr(
|
||||||
|
"SCRAM authentication is not supported by this driver. You need JDK >= 8 and pgjdbc >= 42.2.0 (not \".jre\" versions)",
|
||||||
|
areq), PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUTH_REQ_SASL_CONTINUE:
|
||||||
|
scramAuthenticator.processServerFirstMessage(msgLen - 4 - 4);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUTH_REQ_SASL_FINAL:
|
||||||
|
scramAuthenticator.verifyServerSignature(msgLen - 4 - 4);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUTH_REQ_OK:
|
||||||
|
/* Cleanup after successful authentication */
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE AuthenticationOk");
|
||||||
|
break authloop; // We're done.
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOGGER.log(Level.FINEST, " <=BE AuthenticationReq (unsupported type {0})", areq);
|
||||||
|
throw new PSQLException(GT.tr(
|
||||||
|
"The authentication type {0} is not supported. Check that you have configured the pg_hba.conf file to include the client''s IP address or subnet, and that it is using an authentication scheme supported by the driver.",
|
||||||
|
areq), PSQLState.CONNECTION_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new PSQLException(GT.tr("Protocol error. Session setup failed."),
|
||||||
|
PSQLState.PROTOCOL_VIOLATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void runInitialQueries(QueryExecutor queryExecutor, Properties info)
|
||||||
|
throws SQLException {
|
||||||
|
String assumeMinServerVersion = PGProperty.ASSUME_MIN_SERVER_VERSION.getOrDefault(info);
|
||||||
|
if (Utils.parseServerVersionStr(assumeMinServerVersion) >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
// We already sent the parameter values in the StartupMessage so skip this
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int dbVersion = queryExecutor.getServerVersionNum();
|
||||||
|
|
||||||
|
if (PGProperty.GROUP_STARTUP_PARAMETERS.getBoolean(info) && dbVersion >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
SetupQueryRunner.run(queryExecutor, "BEGIN", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbVersion >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
SetupQueryRunner.run(queryExecutor, "SET extra_float_digits = 3", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String appName = PGProperty.APPLICATION_NAME.getOrDefault(info);
|
||||||
|
if (appName != null && dbVersion >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
StringBuilder sql = new StringBuilder();
|
||||||
|
sql.append("SET application_name = '");
|
||||||
|
Utils.escapeLiteral(sql, appName, queryExecutor.getStandardConformingStrings());
|
||||||
|
sql.append("'");
|
||||||
|
SetupQueryRunner.run(queryExecutor, sql.toString(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PGProperty.GROUP_STARTUP_PARAMETERS.getBoolean(info) && dbVersion >= ServerVersion.v9_0.getVersionNum()) {
|
||||||
|
SetupQueryRunner.run(queryExecutor, "COMMIT", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since PG14 there is GUC_REPORT ParamStatus {@code in_hot_standby} which is set to "on"
|
||||||
|
* when the server is in archive recovery or standby mode. In driver's lingo such server is called
|
||||||
|
* {@link org.postgresql.hostchooser.HostRequirement#secondary}.
|
||||||
|
* Previously {@code transaction_read_only} was used as a workable substitute.
|
||||||
|
* However {@code transaction_read_only} could have been manually overridden on the primary server
|
||||||
|
* by database user leading to a false positives: ie server is effectively read-only but
|
||||||
|
* technically is "primary" (not in a recovery/standby mode).
|
||||||
|
*
|
||||||
|
* <p>This method checks whether {@code in_hot_standby} GUC was reported by the server
|
||||||
|
* during initial connection:</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code in_hot_standby} was reported and the value was "on" then the server is a replica
|
||||||
|
* and database is read-only by definition, false is returned.</li>
|
||||||
|
* <li>{@code in_hot_standby} was reported and the value was "off"
|
||||||
|
* then the server is indeed primary but database may be in
|
||||||
|
* read-only mode nevertheless. We proceed to conservatively {@code show transaction_read_only}
|
||||||
|
* since users may not be expecting a readonly connection for {@code targetServerType=primary}</li>
|
||||||
|
* <li>If {@code in_hot_standby} has not been reported we fallback to pre v14 behavior.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Do not confuse {@code hot_standby} and {@code in_hot_standby} ParamStatuses</p>
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-ASYNC">GUC_REPORT documentation</a>
|
||||||
|
* @see <a href="https://www.postgresql.org/docs/current/hot-standby.html">Hot standby documentation</a>
|
||||||
|
* @see <a href="https://www.postgresql.org/message-id/flat/1700970.cRWpxnom9y@hammer.magicstack.net">in_hot_standby patch thread v10</a>
|
||||||
|
* @see <a href="https://www.postgresql.org/message-id/flat/CAF3%2BxM%2B8-ztOkaV9gHiJ3wfgENTq97QcjXQt%2BrbFQ6F7oNzt9A%40mail.gmail.com">in_hot_standby patch thread v14</a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private boolean isPrimary(QueryExecutor queryExecutor) throws SQLException, IOException {
|
||||||
|
String inHotStandby = queryExecutor.getParameterStatus(IN_HOT_STANDBY);
|
||||||
|
if ("on".equalsIgnoreCase(inHotStandby)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Tuple results = SetupQueryRunner.run(queryExecutor, "show transaction_read_only", true);
|
||||||
|
Tuple nonNullResults = results;
|
||||||
|
String queriedTransactionReadonly = queryExecutor.getEncoding().decode(nonNullResults.get(0));
|
||||||
|
return "off".equalsIgnoreCase(queriedTransactionReadonly);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2016, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.copy.CopyDual;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
public class CopyDualImpl extends CopyOperationImpl implements CopyDual {
|
||||||
|
private final Queue<byte[]> received = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public CopyDualImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(byte[] data, int off, int siz) throws SQLException {
|
||||||
|
getQueryExecutor().writeToCopy(this, data, off, siz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(ByteStreamWriter from) throws SQLException {
|
||||||
|
getQueryExecutor().writeToCopy(this, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushCopy() throws SQLException {
|
||||||
|
getQueryExecutor().flushCopy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long endCopy() throws SQLException {
|
||||||
|
return getQueryExecutor().endCopy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy() throws SQLException {
|
||||||
|
return readFromCopy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy(boolean block) throws SQLException {
|
||||||
|
if (received.isEmpty()) {
|
||||||
|
getQueryExecutor().readFromCopy(this, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
return received.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommandStatus(String status) throws PSQLException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleCopydata(byte[] data) {
|
||||||
|
received.add(data);
|
||||||
|
}
|
||||||
|
}
|
65
pgjdbc/src/main/java/org/postgresql/core/v3/CopyInImpl.java
Normal file
65
pgjdbc/src/main/java/org/postgresql/core/v3/CopyInImpl.java
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.copy.CopyIn;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>COPY FROM STDIN operation.</p>
|
||||||
|
*
|
||||||
|
* <p>Anticipated flow:
|
||||||
|
*
|
||||||
|
* CopyManager.copyIn() ->QueryExecutor.startCopy() - sends given query to server
|
||||||
|
* ->processCopyResults(): - receives CopyInResponse from Server - creates new CopyInImpl
|
||||||
|
* ->initCopy(): - receives copy metadata from server ->CopyInImpl.init() ->lock()
|
||||||
|
* connection for this operation - if query fails an exception is thrown - if query returns wrong
|
||||||
|
* CopyOperation, copyIn() cancels it before throwing exception <-return: new CopyInImpl holding
|
||||||
|
* lock on connection repeat CopyIn.writeToCopy() for all data ->CopyInImpl.writeToCopy()
|
||||||
|
* ->QueryExecutorImpl.writeToCopy() - sends given data ->processCopyResults() - parameterized
|
||||||
|
* not to block, just peek for new messages from server - on ErrorResponse, waits until protocol is
|
||||||
|
* restored and unlocks connection CopyIn.endCopy() ->CopyInImpl.endCopy()
|
||||||
|
* ->QueryExecutorImpl.endCopy() - sends CopyDone - processCopyResults() - on CommandComplete
|
||||||
|
* ->CopyOperationImpl.handleCommandComplete() - sets updatedRowCount when applicable - on
|
||||||
|
* ReadyForQuery unlock() connection for use by other operations <-return:
|
||||||
|
* CopyInImpl.getUpdatedRowCount()</p>
|
||||||
|
*/
|
||||||
|
public class CopyInImpl extends CopyOperationImpl implements CopyIn {
|
||||||
|
|
||||||
|
public CopyInImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(byte[] data, int off, int siz) throws SQLException {
|
||||||
|
getQueryExecutor().writeToCopy(this, data, off, siz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToCopy(ByteStreamWriter from) throws SQLException {
|
||||||
|
getQueryExecutor().writeToCopy(this, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushCopy() throws SQLException {
|
||||||
|
getQueryExecutor().flushCopy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long endCopy() throws SQLException {
|
||||||
|
return getQueryExecutor().endCopy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleCopydata(byte[] data) throws PSQLException {
|
||||||
|
throw new PSQLException(GT.tr("CopyIn copy direction can't receive data"),
|
||||||
|
PSQLState.PROTOCOL_VIOLATION);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.copy.CopyOperation;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public abstract class CopyOperationImpl implements CopyOperation {
|
||||||
|
QueryExecutorImpl queryExecutor;
|
||||||
|
int rowFormat;
|
||||||
|
int [] fieldFormats;
|
||||||
|
long handledRowCount = -1;
|
||||||
|
|
||||||
|
public CopyOperationImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(QueryExecutorImpl q, int fmt, int[] fmts) {
|
||||||
|
queryExecutor = q;
|
||||||
|
rowFormat = fmt;
|
||||||
|
fieldFormats = fmts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QueryExecutorImpl getQueryExecutor() {
|
||||||
|
return queryExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelCopy() throws SQLException {
|
||||||
|
queryExecutor.cancelCopy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldCount() {
|
||||||
|
return fieldFormats.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFieldFormat(int field) {
|
||||||
|
return fieldFormats[field];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFormat() {
|
||||||
|
return rowFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return queryExecutor.hasLockOn(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleCommandStatus(String status) throws PSQLException {
|
||||||
|
if (status.startsWith("COPY")) {
|
||||||
|
int i = status.lastIndexOf(' ');
|
||||||
|
handledRowCount = i > 3 ? Long.parseLong(status.substring(i + 1)) : -1;
|
||||||
|
} else {
|
||||||
|
throw new PSQLException(GT.tr("CommandComplete expected COPY but got: " + status),
|
||||||
|
PSQLState.COMMUNICATION_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume received copy data.
|
||||||
|
*
|
||||||
|
* @param data data that was receive by copy protocol
|
||||||
|
* @throws PSQLException if some internal problem occurs
|
||||||
|
*/
|
||||||
|
protected abstract void handleCopydata(byte[] data) throws PSQLException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getHandledRowCount() {
|
||||||
|
return handledRowCount;
|
||||||
|
}
|
||||||
|
}
|
48
pgjdbc/src/main/java/org/postgresql/core/v3/CopyOutImpl.java
Normal file
48
pgjdbc/src/main/java/org/postgresql/core/v3/CopyOutImpl.java
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2009, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.copy.CopyOut;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Anticipated flow of a COPY TO STDOUT operation:</p>
|
||||||
|
*
|
||||||
|
* <p>CopyManager.copyOut() ->QueryExecutor.startCopy() - sends given query to server
|
||||||
|
* ->processCopyResults(): - receives CopyOutResponse from Server - creates new CopyOutImpl
|
||||||
|
* ->initCopy(): - receives copy metadata from server ->CopyOutImpl.init() ->lock()
|
||||||
|
* connection for this operation - if query fails an exception is thrown - if query returns wrong
|
||||||
|
* CopyOperation, copyOut() cancels it before throwing exception <-returned: new CopyOutImpl
|
||||||
|
* holding lock on connection repeat CopyOut.readFromCopy() until null
|
||||||
|
* ->CopyOutImpl.readFromCopy() ->QueryExecutorImpl.readFromCopy() ->processCopyResults() -
|
||||||
|
* on copydata row from server ->CopyOutImpl.handleCopydata() stores reference to byte array - on
|
||||||
|
* CopyDone, CommandComplete, ReadyForQuery ->unlock() connection for use by other operations
|
||||||
|
* <-returned: byte array of data received from server or null at end.</p>
|
||||||
|
*/
|
||||||
|
public class CopyOutImpl extends CopyOperationImpl implements CopyOut {
|
||||||
|
private byte [] currentDataRow;
|
||||||
|
|
||||||
|
public CopyOutImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy() throws SQLException {
|
||||||
|
return readFromCopy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte [] readFromCopy(boolean block) throws SQLException {
|
||||||
|
currentDataRow = null;
|
||||||
|
getQueryExecutor().readFromCopy(this, block);
|
||||||
|
return currentDataRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleCopydata(byte[] data) {
|
||||||
|
currentDataRow = data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information for "pending describe queue".
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DescribeRequest {
|
||||||
|
public final SimpleQuery query;
|
||||||
|
public final SimpleParameterList parameterList;
|
||||||
|
public final boolean describeOnly;
|
||||||
|
public final String statementName;
|
||||||
|
|
||||||
|
DescribeRequest(SimpleQuery query, SimpleParameterList parameterList,
|
||||||
|
boolean describeOnly, String statementName) {
|
||||||
|
this.query = query;
|
||||||
|
this.parameterList = parameterList;
|
||||||
|
this.describeOnly = describeOnly;
|
||||||
|
this.statementName = statementName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2015, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information for "pending execute queue".
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ExecuteRequest {
|
||||||
|
public final SimpleQuery query;
|
||||||
|
public final Portal portal;
|
||||||
|
public final boolean asSimple;
|
||||||
|
|
||||||
|
ExecuteRequest(SimpleQuery query, Portal portal, boolean asSimple) {
|
||||||
|
this.query = query;
|
||||||
|
this.portal = portal;
|
||||||
|
this.asSimple = asSimple;
|
||||||
|
}
|
||||||
|
}
|
68
pgjdbc/src/main/java/org/postgresql/core/v3/Portal.java
Normal file
68
pgjdbc/src/main/java/org/postgresql/core/v3/Portal.java
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.ResultCursor;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V3 ResultCursor implementation in terms of backend Portals. This holds the state of a single
|
||||||
|
* Portal. We use a PhantomReference managed by our caller to handle resource cleanup.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
class Portal implements ResultCursor {
|
||||||
|
Portal(SimpleQuery query, String portalName) {
|
||||||
|
this.query = query;
|
||||||
|
this.portalName = portalName;
|
||||||
|
this.encodedName = portalName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
PhantomReference<?> cleanupRef = this.cleanupRef;
|
||||||
|
if (cleanupRef != null) {
|
||||||
|
cleanupRef.clear();
|
||||||
|
cleanupRef.enqueue();
|
||||||
|
this.cleanupRef = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPortalName() {
|
||||||
|
return portalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getEncodedPortalName() {
|
||||||
|
return encodedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleQuery getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCleanupRef(PhantomReference<?> cleanupRef) {
|
||||||
|
this.cleanupRef = cleanupRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return portalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holding on to a reference to the generating query has
|
||||||
|
// the nice side-effect that while this Portal is referenced,
|
||||||
|
// so is the SimpleQuery, so the underlying statement won't
|
||||||
|
// be closed while the portal is open (the backend closes
|
||||||
|
// all open portals when the statement is closed)
|
||||||
|
|
||||||
|
private final SimpleQuery query;
|
||||||
|
private final String portalName;
|
||||||
|
private final byte[] encodedName;
|
||||||
|
private PhantomReference<?> cleanupRef;
|
||||||
|
}
|
3102
pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
Normal file
3102
pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,623 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.Oid;
|
||||||
|
import org.postgresql.core.PGStream;
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
import org.postgresql.core.Utils;
|
||||||
|
import org.postgresql.geometric.PGbox;
|
||||||
|
import org.postgresql.geometric.PGpoint;
|
||||||
|
import org.postgresql.jdbc.UUIDArrayAssistant;
|
||||||
|
import org.postgresql.util.ByteConverter;
|
||||||
|
import org.postgresql.util.ByteStreamWriter;
|
||||||
|
import org.postgresql.util.GT;
|
||||||
|
import org.postgresql.util.PSQLException;
|
||||||
|
import org.postgresql.util.PSQLState;
|
||||||
|
import org.postgresql.util.StreamWrapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter list for a single-statement V3 query.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
class SimpleParameterList implements V3ParameterList {
|
||||||
|
|
||||||
|
private static final byte IN = 1;
|
||||||
|
private static final byte OUT = 2;
|
||||||
|
private static final byte INOUT = IN | OUT;
|
||||||
|
|
||||||
|
private static final byte TEXT = 0;
|
||||||
|
private static final byte BINARY = 4;
|
||||||
|
|
||||||
|
SimpleParameterList(int paramCount, TypeTransferModeRegistry transferModeRegistry) {
|
||||||
|
this.paramValues = new Object[paramCount];
|
||||||
|
this.paramTypes = new int[paramCount];
|
||||||
|
this.encoded = new byte[paramCount][];
|
||||||
|
this.flags = new byte[paramCount];
|
||||||
|
this.transferModeRegistry = transferModeRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerOutParameter(int index, int sqlType) throws SQLException {
|
||||||
|
if (index < 1 || index > paramValues.length) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The column index is out of range: {0}, number of columns: {1}.",
|
||||||
|
index, paramValues.length),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
flags[index - 1] |= OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bind(int index, Object value, int oid, byte binary) throws SQLException {
|
||||||
|
if (index < 1 || index > paramValues.length) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("The column index is out of range: {0}, number of columns: {1}.",
|
||||||
|
index, paramValues.length),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
--index;
|
||||||
|
|
||||||
|
encoded[index] = null;
|
||||||
|
paramValues[index] = value;
|
||||||
|
flags[index] = (byte) (direction(index) | IN | binary);
|
||||||
|
|
||||||
|
// If we are setting something to an UNSPECIFIED NULL, don't overwrite
|
||||||
|
// our existing type for it. We don't need the correct type info to
|
||||||
|
// send this value, and we don't want to overwrite and require a
|
||||||
|
// reparse.
|
||||||
|
if (oid == Oid.UNSPECIFIED && paramTypes[index] != Oid.UNSPECIFIED && value == NULL_OBJECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
paramTypes[index] = oid;
|
||||||
|
pos = index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getParameterCount() {
|
||||||
|
return paramValues.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOutParameterCount() {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if ((direction(i) & OUT) == OUT) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Every function has at least one output.
|
||||||
|
if (count == 0) {
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInParameterCount() {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if (direction(i) != OUT) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIntParameter(int index, int value) throws SQLException {
|
||||||
|
byte[] data = new byte[4];
|
||||||
|
ByteConverter.int4(data, 0, value);
|
||||||
|
bind(index, data, Oid.INT4, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLiteralParameter(int index, String value, int oid) throws SQLException {
|
||||||
|
bind(index, value, oid, TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStringParameter(int index, String value, int oid) throws SQLException {
|
||||||
|
bind(index, value, oid, TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBinaryParameter(int index, byte[] value, int oid) throws SQLException {
|
||||||
|
bind(index, value, oid, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, byte[] data, int offset, int length) throws SQLException {
|
||||||
|
bind(index, new StreamWrapper(data, offset, length), Oid.BYTEA, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, InputStream stream, int length) throws SQLException {
|
||||||
|
bind(index, new StreamWrapper(stream, length), Oid.BYTEA, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, InputStream stream) throws SQLException {
|
||||||
|
bind(index, new StreamWrapper(stream), Oid.BYTEA, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytea(int index, ByteStreamWriter writer) throws SQLException {
|
||||||
|
bind(index, writer, Oid.BYTEA, BINARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setText(int index, InputStream stream) throws SQLException {
|
||||||
|
bind(index, new StreamWrapper(stream), Oid.TEXT, TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNull(int index, int oid) throws SQLException {
|
||||||
|
|
||||||
|
byte binaryTransfer = TEXT;
|
||||||
|
|
||||||
|
if (transferModeRegistry != null && transferModeRegistry.useBinaryForReceive(oid)) {
|
||||||
|
binaryTransfer = BINARY;
|
||||||
|
}
|
||||||
|
bind(index, NULL_OBJECT, oid, binaryTransfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Escapes a given text value as a literal, wraps it in single quotes, casts it to the
|
||||||
|
* to the given data type, and finally wraps the whole thing in parentheses.</p>
|
||||||
|
*
|
||||||
|
* <p>For example, "123" and "int4" becomes "('123'::int)"</p>
|
||||||
|
*
|
||||||
|
* <p>The additional parentheses is added to ensure that the surrounding text of where the
|
||||||
|
* parameter value is entered does modify the interpretation of the value.</p>
|
||||||
|
*
|
||||||
|
* <p>For example if our input SQL is: <code>SELECT ?b</code></p>
|
||||||
|
*
|
||||||
|
* <p>Using a parameter value of '{}' and type of json we'd get:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* test=# SELECT ('{}'::json)b;
|
||||||
|
* b
|
||||||
|
* ----
|
||||||
|
* {}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>But without the parentheses the result changes:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* test=# SELECT '{}'::jsonb;
|
||||||
|
* jsonb
|
||||||
|
* -------
|
||||||
|
* {}
|
||||||
|
* </pre>
|
||||||
|
**/
|
||||||
|
private static String quoteAndCast(String text, String type, boolean standardConformingStrings) {
|
||||||
|
StringBuilder sb = new StringBuilder((text.length() + 10) / 10 * 11); // Add 10% for escaping.
|
||||||
|
sb.append("('");
|
||||||
|
try {
|
||||||
|
Utils.escapeLiteral(sb, text, standardConformingStrings);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// This should only happen if we have an embedded null
|
||||||
|
// and there's not much we can do if we do hit one.
|
||||||
|
//
|
||||||
|
// To force a server side failure, we deliberately include
|
||||||
|
// a zero byte character in the literal to force the server
|
||||||
|
// to reject the command.
|
||||||
|
sb.append('\u0000');
|
||||||
|
}
|
||||||
|
sb.append("'");
|
||||||
|
if (type != null) {
|
||||||
|
sb.append("::");
|
||||||
|
sb.append(type);
|
||||||
|
}
|
||||||
|
sb.append(")");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(int index, boolean standardConformingStrings) {
|
||||||
|
--index;
|
||||||
|
Object paramValue = paramValues[index];
|
||||||
|
if (paramValue == null) {
|
||||||
|
return "?";
|
||||||
|
} else if (paramValue == NULL_OBJECT) {
|
||||||
|
return "(NULL)";
|
||||||
|
}
|
||||||
|
String textValue;
|
||||||
|
String type;
|
||||||
|
if ((flags[index] & BINARY) == BINARY) {
|
||||||
|
// handle some of the numeric types
|
||||||
|
switch (paramTypes[index]) {
|
||||||
|
case Oid.INT2:
|
||||||
|
short s = ByteConverter.int2((byte[]) paramValue, 0);
|
||||||
|
textValue = Short.toString(s);
|
||||||
|
type = "int2";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.INT4:
|
||||||
|
int i = ByteConverter.int4((byte[]) paramValue, 0);
|
||||||
|
textValue = Integer.toString(i);
|
||||||
|
type = "int4";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.INT8:
|
||||||
|
long l = ByteConverter.int8((byte[]) paramValue, 0);
|
||||||
|
textValue = Long.toString(l);
|
||||||
|
type = "int8";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.FLOAT4:
|
||||||
|
float f = ByteConverter.float4((byte[]) paramValue, 0);
|
||||||
|
if (Float.isNaN(f)) {
|
||||||
|
return "('NaN'::real)";
|
||||||
|
}
|
||||||
|
textValue = Float.toString(f);
|
||||||
|
type = "real";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.FLOAT8:
|
||||||
|
double d = ByteConverter.float8((byte[]) paramValue, 0);
|
||||||
|
if (Double.isNaN(d)) {
|
||||||
|
return "('NaN'::double precision)";
|
||||||
|
}
|
||||||
|
textValue = Double.toString(d);
|
||||||
|
type = "double precision";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.NUMERIC:
|
||||||
|
Number n = ByteConverter.numeric((byte[]) paramValue);
|
||||||
|
if (n instanceof Double) {
|
||||||
|
assert ((Double) n).isNaN();
|
||||||
|
return "('NaN'::numeric)";
|
||||||
|
}
|
||||||
|
textValue = n.toString();
|
||||||
|
type = "numeric";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.UUID:
|
||||||
|
textValue =
|
||||||
|
new UUIDArrayAssistant().buildElement((byte[]) paramValue, 0, 16).toString();
|
||||||
|
type = "uuid";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.POINT:
|
||||||
|
PGpoint pgPoint = new PGpoint();
|
||||||
|
pgPoint.setByteValue((byte[]) paramValue, 0);
|
||||||
|
textValue = pgPoint.toString();
|
||||||
|
type = "point";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Oid.BOX:
|
||||||
|
PGbox pgBox = new PGbox();
|
||||||
|
pgBox.setByteValue((byte[]) paramValue, 0);
|
||||||
|
textValue = pgBox.toString();
|
||||||
|
type = "box";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textValue = paramValue.toString();
|
||||||
|
switch (paramTypes[index]) {
|
||||||
|
case Oid.INT2:
|
||||||
|
type = "int2";
|
||||||
|
break;
|
||||||
|
case Oid.INT4:
|
||||||
|
type = "int4";
|
||||||
|
break;
|
||||||
|
case Oid.INT8:
|
||||||
|
type = "int8";
|
||||||
|
break;
|
||||||
|
case Oid.FLOAT4:
|
||||||
|
type = "real";
|
||||||
|
break;
|
||||||
|
case Oid.FLOAT8:
|
||||||
|
type = "double precision";
|
||||||
|
break;
|
||||||
|
case Oid.TIMESTAMP:
|
||||||
|
type = "timestamp";
|
||||||
|
break;
|
||||||
|
case Oid.TIMESTAMPTZ:
|
||||||
|
type = "timestamp with time zone";
|
||||||
|
break;
|
||||||
|
case Oid.TIME:
|
||||||
|
type = "time";
|
||||||
|
break;
|
||||||
|
case Oid.TIMETZ:
|
||||||
|
type = "time with time zone";
|
||||||
|
break;
|
||||||
|
case Oid.DATE:
|
||||||
|
type = "date";
|
||||||
|
break;
|
||||||
|
case Oid.INTERVAL:
|
||||||
|
type = "interval";
|
||||||
|
break;
|
||||||
|
case Oid.NUMERIC:
|
||||||
|
type = "numeric";
|
||||||
|
break;
|
||||||
|
case Oid.UUID:
|
||||||
|
type = "uuid";
|
||||||
|
break;
|
||||||
|
case Oid.BOOL:
|
||||||
|
type = "boolean";
|
||||||
|
break;
|
||||||
|
case Oid.BOX:
|
||||||
|
type = "box";
|
||||||
|
break;
|
||||||
|
case Oid.POINT:
|
||||||
|
type = "point";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return quoteAndCast(textValue, type, standardConformingStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkAllParametersSet() throws SQLException {
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if (direction(i) != OUT && paramValues[i] == null) {
|
||||||
|
throw new PSQLException(GT.tr("No value specified for parameter {0}.", i + 1),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void convertFunctionOutParameters() {
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
if (direction(i) == OUT) {
|
||||||
|
paramTypes[i] = Oid.VOID;
|
||||||
|
paramValues[i] = NULL_OBJECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// bytea helper
|
||||||
|
//
|
||||||
|
|
||||||
|
private static void streamBytea(PGStream pgStream, StreamWrapper wrapper) throws IOException {
|
||||||
|
byte[] rawData = wrapper.getBytes();
|
||||||
|
if (rawData != null) {
|
||||||
|
pgStream.send(rawData, wrapper.getOffset(), wrapper.getLength());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStream.sendStream(wrapper.getStream(), wrapper.getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// byte stream writer support
|
||||||
|
//
|
||||||
|
|
||||||
|
private static void streamBytea(PGStream pgStream, ByteStreamWriter writer) throws IOException {
|
||||||
|
pgStream.send(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getTypeOIDs() {
|
||||||
|
return paramTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Package-private V3 accessors
|
||||||
|
//
|
||||||
|
|
||||||
|
int getTypeOID(int index) {
|
||||||
|
return paramTypes[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasUnresolvedTypes() {
|
||||||
|
for (int paramType : paramTypes) {
|
||||||
|
if (paramType == Oid.UNSPECIFIED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setResolvedType(int index, int oid) {
|
||||||
|
// only allow overwriting an unknown value or VOID value
|
||||||
|
if (paramTypes[index - 1] == Oid.UNSPECIFIED || paramTypes[index - 1] == Oid.VOID) {
|
||||||
|
paramTypes[index - 1] = oid;
|
||||||
|
} else if (paramTypes[index - 1] != oid) {
|
||||||
|
throw new IllegalArgumentException("Can't change resolved type for param: " + index + " from "
|
||||||
|
+ paramTypes[index - 1] + " to " + oid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isNull(int index) {
|
||||||
|
return paramValues[index - 1] == NULL_OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isBinary(int index) {
|
||||||
|
return (flags[index - 1] & BINARY) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte direction(int index) {
|
||||||
|
return (byte) (flags[index] & INOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getV3Length(int index) {
|
||||||
|
--index;
|
||||||
|
|
||||||
|
// Null?
|
||||||
|
Object value = paramValues[index];
|
||||||
|
if (value == null || value == NULL_OBJECT) {
|
||||||
|
throw new IllegalArgumentException("can't getV3Length() on a null parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly encoded?
|
||||||
|
if (value instanceof byte[]) {
|
||||||
|
return ((byte[]) value).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary-format bytea?
|
||||||
|
if (value instanceof StreamWrapper) {
|
||||||
|
return ((StreamWrapper) value).getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary-format bytea?
|
||||||
|
if (value instanceof ByteStreamWriter) {
|
||||||
|
return ((ByteStreamWriter) value).getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already encoded?
|
||||||
|
byte[] encoded = this.encoded[index];
|
||||||
|
if (encoded == null) {
|
||||||
|
// Encode value and compute actual length using UTF-8.
|
||||||
|
this.encoded[index] = encoded = value.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeV3Value(int index, PGStream pgStream) throws IOException {
|
||||||
|
--index;
|
||||||
|
|
||||||
|
// Null?
|
||||||
|
Object paramValue = paramValues[index];
|
||||||
|
if (paramValue == null || paramValue == NULL_OBJECT) {
|
||||||
|
throw new IllegalArgumentException("can't writeV3Value() on a null parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly encoded?
|
||||||
|
if (paramValue instanceof byte[]) {
|
||||||
|
pgStream.send((byte[]) paramValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary-format bytea?
|
||||||
|
if (paramValue instanceof StreamWrapper) {
|
||||||
|
try (StreamWrapper streamWrapper = (StreamWrapper) paramValue) {
|
||||||
|
streamBytea(pgStream, streamWrapper);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streamed bytea?
|
||||||
|
if (paramValue instanceof ByteStreamWriter) {
|
||||||
|
streamBytea(pgStream, (ByteStreamWriter) paramValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoded string.
|
||||||
|
if (encoded[index] == null) {
|
||||||
|
encoded[index] = ((String) paramValue).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
pgStream.send(encoded[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterList copy() {
|
||||||
|
SimpleParameterList newCopy = new SimpleParameterList(paramValues.length, transferModeRegistry);
|
||||||
|
System.arraycopy(paramValues, 0, newCopy.paramValues, 0, paramValues.length);
|
||||||
|
System.arraycopy(paramTypes, 0, newCopy.paramTypes, 0, paramTypes.length);
|
||||||
|
System.arraycopy(flags, 0, newCopy.flags, 0, flags.length);
|
||||||
|
newCopy.pos = pos;
|
||||||
|
return newCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
Arrays.fill(paramValues, null);
|
||||||
|
Arrays.fill(paramTypes, 0);
|
||||||
|
Arrays.fill(encoded, null);
|
||||||
|
Arrays.fill(flags, (byte) 0);
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleParameterList [] getSubparams() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getValues() {
|
||||||
|
return paramValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getParamTypes() {
|
||||||
|
return paramTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] [] getEncoding() {
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendAll(ParameterList list) throws SQLException {
|
||||||
|
if (list instanceof SimpleParameterList ) {
|
||||||
|
/* only v3.SimpleParameterList is compatible with this type
|
||||||
|
we need to create copies of our parameters, otherwise the values can be changed */
|
||||||
|
SimpleParameterList spl = (SimpleParameterList) list;
|
||||||
|
int inParamCount = spl.getInParameterCount();
|
||||||
|
if ((pos + inParamCount) > paramValues.length) {
|
||||||
|
throw new PSQLException(
|
||||||
|
GT.tr("Added parameters index out of range: {0}, number of columns: {1}.",
|
||||||
|
(pos + inParamCount), paramValues.length),
|
||||||
|
PSQLState.INVALID_PARAMETER_VALUE);
|
||||||
|
}
|
||||||
|
System.arraycopy(spl.getValues(), 0, this.paramValues, pos, inParamCount);
|
||||||
|
System.arraycopy(spl.getParamTypes(), 0, this.paramTypes, pos, inParamCount);
|
||||||
|
System.arraycopy(spl.getFlags(), 0, this.flags, pos, inParamCount);
|
||||||
|
System.arraycopy(spl.getEncoding(), 0, this.encoded, pos, inParamCount);
|
||||||
|
pos += inParamCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful implementation of toString.
|
||||||
|
* @return String representation of the list values
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder ts = new StringBuilder("<[");
|
||||||
|
if (paramValues.length > 0) {
|
||||||
|
ts.append(toString(1, true));
|
||||||
|
for (int c = 2; c <= paramValues.length; c++) {
|
||||||
|
ts.append(" ,").append(toString(c, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.append("]>");
|
||||||
|
return ts.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object[] paramValues;
|
||||||
|
private final int[] paramTypes;
|
||||||
|
private final byte[] flags;
|
||||||
|
private final byte[] [] encoded;
|
||||||
|
private final TypeTransferModeRegistry transferModeRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker object representing NULL; this distinguishes "parameter never set" from "parameter set
|
||||||
|
* to null".
|
||||||
|
*/
|
||||||
|
private static final Object NULL_OBJECT = new Object();
|
||||||
|
|
||||||
|
private int pos;
|
||||||
|
}
|
381
pgjdbc/src/main/java/org/postgresql/core/v3/SimpleQuery.java
Normal file
381
pgjdbc/src/main/java/org/postgresql/core/v3/SimpleQuery.java
Normal file
|
@ -0,0 +1,381 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.Field;
|
||||||
|
import org.postgresql.core.NativeQuery;
|
||||||
|
import org.postgresql.core.Oid;
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
import org.postgresql.core.Query;
|
||||||
|
import org.postgresql.core.SqlCommand;
|
||||||
|
import org.postgresql.jdbc.PgResultSet;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V3 Query implementation for a single-statement query. This also holds the state of any associated
|
||||||
|
* server-side named statement. We use a PhantomReference managed by the QueryExecutor to handle
|
||||||
|
* statement cleanup.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
class SimpleQuery implements Query {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SimpleQuery.class.getName());
|
||||||
|
|
||||||
|
SimpleQuery(SimpleQuery src) {
|
||||||
|
this(src.nativeQuery, src.transferModeRegistry, src.sanitiserDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleQuery(NativeQuery query, TypeTransferModeRegistry transferModeRegistry,
|
||||||
|
boolean sanitiserDisabled) {
|
||||||
|
this.nativeQuery = query;
|
||||||
|
this.transferModeRegistry = transferModeRegistry;
|
||||||
|
this.sanitiserDisabled = sanitiserDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParameterList createParameterList() {
|
||||||
|
if (nativeQuery.bindPositions.length == 0) {
|
||||||
|
return NO_PARAMETERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SimpleParameterList(getBindCount(), transferModeRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(ParameterList parameters) {
|
||||||
|
return nativeQuery.toString(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return toString(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
unprepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleQuery [] getSubqueries() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Return maximum size in bytes that each result row from this query may return. Mainly used for
|
||||||
|
* batches that return results.</p>
|
||||||
|
*
|
||||||
|
* <p>Results are cached until/unless the query is re-described.</p>
|
||||||
|
*
|
||||||
|
* @return Max size of result data in bytes according to returned fields, 0 if no results, -1 if
|
||||||
|
* result is unbounded.
|
||||||
|
* @throws IllegalStateException if the query is not described
|
||||||
|
*/
|
||||||
|
public int getMaxResultRowSize() {
|
||||||
|
if (cachedMaxResultRowSize != null) {
|
||||||
|
return cachedMaxResultRowSize;
|
||||||
|
}
|
||||||
|
if (!this.statementDescribed) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot estimate result row size on a statement that is not described");
|
||||||
|
}
|
||||||
|
int maxResultRowSize = 0;
|
||||||
|
if (fields != null) {
|
||||||
|
for (Field f : fields) {
|
||||||
|
final int fieldLength = f.getLength();
|
||||||
|
if (fieldLength < 1 || fieldLength >= 65535) {
|
||||||
|
/*
|
||||||
|
* Field length unknown or large; we can't make any safe estimates about the result size,
|
||||||
|
* so we have to fall back to sending queries individually.
|
||||||
|
*/
|
||||||
|
maxResultRowSize = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maxResultRowSize += fieldLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedMaxResultRowSize = maxResultRowSize;
|
||||||
|
return maxResultRowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Implementation guts
|
||||||
|
//
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNativeSql() {
|
||||||
|
return nativeQuery.nativeSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStatementName(String statementName, short deallocateEpoch) {
|
||||||
|
assert statementName != null : "statement name should not be null";
|
||||||
|
this.statementName = statementName;
|
||||||
|
this.encodedStatementName = statementName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
this.deallocateEpoch = deallocateEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPrepareTypes(int[] paramTypes) {
|
||||||
|
// Remember which parameters were unspecified since the parameters will be overridden later by
|
||||||
|
// ParameterDescription message
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
int paramType = paramTypes[i];
|
||||||
|
if (paramType == Oid.UNSPECIFIED) {
|
||||||
|
if (this.unspecifiedParams == null) {
|
||||||
|
this.unspecifiedParams = new BitSet();
|
||||||
|
}
|
||||||
|
this.unspecifiedParams.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramTypes is changed by "describe statement" response, so we clone the array
|
||||||
|
// However, we can reuse array if there is one
|
||||||
|
if (this.preparedTypes == null) {
|
||||||
|
this.preparedTypes = paramTypes.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System.arraycopy(paramTypes, 0, this.preparedTypes, 0, paramTypes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int [] getPrepareTypes() {
|
||||||
|
return preparedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getStatementName() {
|
||||||
|
return statementName;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPreparedFor(int[] paramTypes, short deallocateEpoch) {
|
||||||
|
if (statementName == null || preparedTypes == null) {
|
||||||
|
return false; // Not prepared.
|
||||||
|
}
|
||||||
|
if (this.deallocateEpoch != deallocateEpoch) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert paramTypes.length == preparedTypes.length
|
||||||
|
: String.format("paramTypes:%1$d preparedTypes:%2$d", paramTypes.length,
|
||||||
|
preparedTypes.length);
|
||||||
|
// Check for compatible types.
|
||||||
|
BitSet unspecified = this.unspecifiedParams;
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
int paramType = paramTypes[i];
|
||||||
|
// Either paramType should match prepared type
|
||||||
|
// Or paramType==UNSPECIFIED and the prepare type was UNSPECIFIED
|
||||||
|
|
||||||
|
// Note: preparedTypes can be updated by "statement describe"
|
||||||
|
// 1) parse(name="S_01", sql="select ?::timestamp", types={UNSPECIFIED})
|
||||||
|
// 2) statement describe: bind 1 type is TIMESTAMP
|
||||||
|
// 3) SimpleQuery.preparedTypes is updated to TIMESTAMP
|
||||||
|
// ...
|
||||||
|
// 4.1) bind(name="S_01", ..., types={TIMESTAMP}) -> OK (since preparedTypes is equal to TIMESTAMP)
|
||||||
|
// 4.2) bind(name="S_01", ..., types={UNSPECIFIED}) -> OK (since the query was initially parsed with UNSPECIFIED)
|
||||||
|
// 4.3) bind(name="S_01", ..., types={DATE}) -> KO, unprepare and parse required
|
||||||
|
|
||||||
|
int preparedType = preparedTypes[i];
|
||||||
|
if (paramType != preparedType
|
||||||
|
&& (paramType != Oid.UNSPECIFIED
|
||||||
|
|| unspecified == null
|
||||||
|
|| !unspecified.get(i))) {
|
||||||
|
if (LOGGER.isLoggable(Level.FINER)) {
|
||||||
|
LOGGER.log(Level.FINER,
|
||||||
|
"Statement {0} does not match new parameter types. Will have to un-prepare it and parse once again."
|
||||||
|
+ " To avoid performance issues, use the same data type for the same bind position. Bind index (1-based) is {1},"
|
||||||
|
+ " preparedType was {2} (after describe {3}), current bind type is {4}",
|
||||||
|
new Object[]{statementName, i + 1,
|
||||||
|
Oid.toString(unspecified != null && unspecified.get(i) ? 0 : preparedType),
|
||||||
|
Oid.toString(preparedType), Oid.toString(paramType)});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasUnresolvedTypes() {
|
||||||
|
if (preparedTypes == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.unspecifiedParams != null && !this.unspecifiedParams.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte [] getEncodedStatementName() {
|
||||||
|
return encodedStatementName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fields that this query will return.
|
||||||
|
*
|
||||||
|
* @param fields The fields that this query will return.
|
||||||
|
*/
|
||||||
|
void setFields(Field [] fields) {
|
||||||
|
this.fields = fields;
|
||||||
|
this.resultSetColumnNameIndexMap = null;
|
||||||
|
this.cachedMaxResultRowSize = null;
|
||||||
|
this.needUpdateFieldFormats = fields != null;
|
||||||
|
this.hasBinaryFields = false; // just in case
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fields that this query will return. If the result set fields are not known returns
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
* @return the fields that this query will return.
|
||||||
|
*/
|
||||||
|
Field [] getFields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if current query needs field formats be adjusted as per connection configuration.
|
||||||
|
* Subsequent invocations would return {@code false}. The idea is to perform adjustments only
|
||||||
|
* once, not for each
|
||||||
|
* {@link QueryExecutorImpl#sendBind(SimpleQuery, SimpleParameterList, Portal, boolean)}
|
||||||
|
*
|
||||||
|
* @return true if current query needs field formats be adjusted as per connection configuration
|
||||||
|
*/
|
||||||
|
boolean needUpdateFieldFormats() {
|
||||||
|
if (needUpdateFieldFormats) {
|
||||||
|
needUpdateFieldFormats = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetNeedUpdateFieldFormats() {
|
||||||
|
needUpdateFieldFormats = fields != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBinaryFields() {
|
||||||
|
return hasBinaryFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasBinaryFields(boolean hasBinaryFields) {
|
||||||
|
this.hasBinaryFields = hasBinaryFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have we sent a Describe Portal message for this query yet?
|
||||||
|
boolean isPortalDescribed() {
|
||||||
|
return portalDescribed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPortalDescribed(boolean portalDescribed) {
|
||||||
|
this.portalDescribed = portalDescribed;
|
||||||
|
this.cachedMaxResultRowSize = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have we sent a Describe Statement message for this query yet?
|
||||||
|
// Note that we might not have need to, so this may always be false.
|
||||||
|
@Override
|
||||||
|
public boolean isStatementDescribed() {
|
||||||
|
return statementDescribed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStatementDescribed(boolean statementDescribed) {
|
||||||
|
this.statementDescribed = statementDescribed;
|
||||||
|
this.cachedMaxResultRowSize = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return getNativeSql().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCleanupRef(PhantomReference<?> cleanupRef) {
|
||||||
|
PhantomReference<?> oldCleanupRef = this.cleanupRef;
|
||||||
|
if (oldCleanupRef != null) {
|
||||||
|
oldCleanupRef.clear();
|
||||||
|
oldCleanupRef.enqueue();
|
||||||
|
}
|
||||||
|
this.cleanupRef = cleanupRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unprepare() {
|
||||||
|
PhantomReference<?> cleanupRef = this.cleanupRef;
|
||||||
|
if (cleanupRef != null) {
|
||||||
|
cleanupRef.clear();
|
||||||
|
cleanupRef.enqueue();
|
||||||
|
this.cleanupRef = null;
|
||||||
|
}
|
||||||
|
if (this.unspecifiedParams != null) {
|
||||||
|
this.unspecifiedParams.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
statementName = null;
|
||||||
|
encodedStatementName = null;
|
||||||
|
fields = null;
|
||||||
|
this.resultSetColumnNameIndexMap = null;
|
||||||
|
portalDescribed = false;
|
||||||
|
statementDescribed = false;
|
||||||
|
cachedMaxResultRowSize = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBatchSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeQuery getNativeQuery() {
|
||||||
|
return nativeQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getBindCount() {
|
||||||
|
return nativeQuery.bindPositions.length * getBatchSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Integer> resultSetColumnNameIndexMap;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> getResultSetColumnNameIndexMap() {
|
||||||
|
Map<String, Integer> columnPositions = this.resultSetColumnNameIndexMap;
|
||||||
|
if (columnPositions == null && fields != null) {
|
||||||
|
columnPositions =
|
||||||
|
PgResultSet.createColumnNameIndexMap(fields, sanitiserDisabled);
|
||||||
|
if (statementName != null) {
|
||||||
|
// Cache column positions for server-prepared statements only
|
||||||
|
this.resultSetColumnNameIndexMap = columnPositions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return columnPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SqlCommand getSqlCommand() {
|
||||||
|
return nativeQuery.getCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final NativeQuery nativeQuery;
|
||||||
|
|
||||||
|
private final TypeTransferModeRegistry transferModeRegistry;
|
||||||
|
private String statementName;
|
||||||
|
private byte [] encodedStatementName;
|
||||||
|
/**
|
||||||
|
* The stored fields from previous execution or describe of a prepared statement. Always null for
|
||||||
|
* non-prepared statements.
|
||||||
|
*/
|
||||||
|
private Field [] fields;
|
||||||
|
private boolean needUpdateFieldFormats;
|
||||||
|
private boolean hasBinaryFields;
|
||||||
|
private boolean portalDescribed;
|
||||||
|
private boolean statementDescribed;
|
||||||
|
private final boolean sanitiserDisabled;
|
||||||
|
private PhantomReference<?> cleanupRef;
|
||||||
|
private int [] preparedTypes;
|
||||||
|
private BitSet unspecifiedParams;
|
||||||
|
private short deallocateEpoch;
|
||||||
|
|
||||||
|
private Integer cachedMaxResultRowSize;
|
||||||
|
|
||||||
|
static final SimpleParameterList NO_PARAMETERS = new SimpleParameterList(0, null);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
public interface TypeTransferModeRegistry {
|
||||||
|
/**
|
||||||
|
* Returns if given oid should be sent in binary format.
|
||||||
|
* @param oid type oid
|
||||||
|
* @return true if given oid should be sent in binary format
|
||||||
|
*/
|
||||||
|
boolean useBinaryForSend(int oid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if given oid should be received in binary format.
|
||||||
|
* @param oid type oid
|
||||||
|
* @return true if given oid should be received in binary format
|
||||||
|
*/
|
||||||
|
boolean useBinaryForReceive(int oid);
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2004, PostgreSQL Global Development Group
|
||||||
|
* See the LICENSE file in the project root for more information.
|
||||||
|
*/
|
||||||
|
// Copyright (c) 2004, Open Cloud Limited.
|
||||||
|
|
||||||
|
package org.postgresql.core.v3;
|
||||||
|
|
||||||
|
import org.postgresql.core.ParameterList;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for all V3 parameter list implementations.
|
||||||
|
*
|
||||||
|
* @author Oliver Jowett (oliver@opencloud.com)
|
||||||
|
*/
|
||||||
|
interface V3ParameterList extends ParameterList {
|
||||||
|
/**
|
||||||
|
* Ensure that all parameters in this list have been assigned values. Return silently if all is
|
||||||
|
* well, otherwise throw an appropriate exception.
|
||||||
|
*
|
||||||
|
* @throws SQLException if not all parameters are set.
|
||||||
|
*/
|
||||||
|
void checkAllParametersSet() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert any function output parameters to the correct type (void) and set an ignorable value
|
||||||
|
* for it.
|
||||||
|
*/
|
||||||
|
void convertFunctionOutParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of the SimpleParameterList objects that make up this parameter list. If this
|
||||||
|
* object is already a SimpleParameterList, returns null (avoids an extra array construction in
|
||||||
|
* the common case).
|
||||||
|
*
|
||||||
|
* @return an array of single-statement parameter lists, or <code>null</code> if this object is
|
||||||
|
* already a single-statement parameter list.
|
||||||
|
*/
|
||||||
|
SimpleParameterList [] getSubparams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the parameter type information.
|
||||||
|
* @return an array of {@link org.postgresql.core.Oid} type information
|
||||||
|
*/
|
||||||
|
int [] getParamTypes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the flags for each parameter.
|
||||||
|
* @return an array of bytes used to store flags.
|
||||||
|
*/
|
||||||
|
byte [] getFlags();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the encoding for each parameter.
|
||||||
|
* @return nested byte array of bytes with encoding information.
|
||||||
|
*/
|
||||||
|
byte [] [] getEncoding();
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue