From c93a85e1229c44ddd6a9072e75fd55d790eca80a Mon Sep 17 00:00:00 2001 From: Vlad Ilyushchenko Date: Sun, 23 Jun 2019 12:09:16 +0100 Subject: [PATCH] net: very raw draft of PostgreSQL wire protocol impl --- core/pom.xml | 7 + .../http/HttpChunkedResponseSocket.java | 2 + .../cutlass/http/HttpConnectionContext.java | 5 +- .../http/HttpMultipartContentListener.java | 3 + .../http/HttpMultipartContentParser.java | 2 + .../questdb/cutlass/http/HttpRawSocket.java | 3 + .../cutlass/http/HttpRequestProcessor.java | 2 + .../cutlass/http/HttpResponseHeader.java | 2 + .../cutlass/http/HttpResponseSink.java | 2 + .../http/processors/JsonQueryProcessor.java | 2 + .../processors/StaticContentProcessor.java | 2 + .../processors/TableStatusCheckProcessor.java | 6 +- .../http/processors/TextImportProcessor.java | 2 + .../questdb/cutlass/pgwire/WireParser.java | 320 ++++++++++++++++++ .../pgwire/WireParserConfiguration.java | 34 ++ .../codecs/AbstractTypePrefixedHeader.java | 47 +++ .../cutlass/pgwire/codecs/Constants.java | 31 ++ .../pgwire/codecs/NetworkByteOrderUtils.java | 42 +++ .../pgwire/codecs/in/QueryRequest.java | 31 ++ .../pgwire/codecs/in/StartupMessage.java} | 25 +- .../out/AuthenticationMD5PasswordMsg.java} | 5 +- .../pgwire/codecs/out/AuthenticationMsg.java | 33 ++ .../pgwire/codecs/out/ParameterStatusMsg.java | 49 +++ .../pgwire/codecs/out/ReadyForQueryMsg.java | 33 ++ .../PeerDisconnectedException.java | 4 +- .../PeerIsSlowToReadException.java | 4 +- .../cutlass/http/IODispatcherTest.java | 31 +- .../cutlass/pgwire/WireParserTest.java | 93 +++++ 28 files changed, 783 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/WireParser.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/WireParserConfiguration.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/AbstractTypePrefixedHeader.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/Constants.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/NetworkByteOrderUtils.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/QueryRequest.java rename core/src/main/java/com/questdb/{network/DisconnectReason.java => cutlass/pgwire/codecs/in/StartupMessage.java} (67%) rename core/src/main/java/com/questdb/cutlass/{http/HttpFlowControlException.java => pgwire/codecs/out/AuthenticationMD5PasswordMsg.java} (89%) create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMsg.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ParameterStatusMsg.java create mode 100644 core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ReadyForQueryMsg.java rename core/src/main/java/com/questdb/{cutlass/http => network}/PeerDisconnectedException.java (91%) rename core/src/main/java/com/questdb/{cutlass/http => network}/PeerIsSlowToReadException.java (91%) create mode 100644 core/src/test/java/com/questdb/cutlass/pgwire/WireParserTest.java diff --git a/core/pom.xml b/core/pom.xml index a8660925a..6c384ff5a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -252,5 +252,12 @@ 2.8.1 test + + + org.postgresql + postgresql + 42.2.5 + test + diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpChunkedResponseSocket.java b/core/src/main/java/com/questdb/cutlass/http/HttpChunkedResponseSocket.java index e84d74a62..fa78fdb64 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpChunkedResponseSocket.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpChunkedResponseSocket.java @@ -23,6 +23,8 @@ package com.questdb.cutlass.http; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.str.CharSink; public interface HttpChunkedResponseSocket extends CharSink { diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpConnectionContext.java b/core/src/main/java/com/questdb/cutlass/http/HttpConnectionContext.java index ba3959c71..28e584d2c 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpConnectionContext.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpConnectionContext.java @@ -25,10 +25,7 @@ package com.questdb.cutlass.http; import com.questdb.log.Log; import com.questdb.log.LogFactory; -import com.questdb.network.IOContext; -import com.questdb.network.IODispatcher; -import com.questdb.network.IOOperation; -import com.questdb.network.NetworkFacade; +import com.questdb.network.*; import com.questdb.std.*; import com.questdb.std.str.DirectByteCharSequence; diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentListener.java b/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentListener.java index fd1b75bfc..91d9049e1 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentListener.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentListener.java @@ -23,6 +23,9 @@ package com.questdb.cutlass.http; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; + public interface HttpMultipartContentListener { void onChunk(HttpRequestHeader partHeader, long lo, long hi); diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentParser.java b/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentParser.java index 846454b99..c8d5cb4c1 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentParser.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpMultipartContentParser.java @@ -23,6 +23,8 @@ package com.questdb.cutlass.http; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.Mutable; import com.questdb.std.Unsafe; import com.questdb.std.str.DirectByteCharSequence; diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpRawSocket.java b/core/src/main/java/com/questdb/cutlass/http/HttpRawSocket.java index 5e19c406e..ade41e7d5 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpRawSocket.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpRawSocket.java @@ -23,6 +23,9 @@ package com.questdb.cutlass.http; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; + public interface HttpRawSocket { long getBufferAddress(); diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpRequestProcessor.java b/core/src/main/java/com/questdb/cutlass/http/HttpRequestProcessor.java index 69335608d..a2ce5f686 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpRequestProcessor.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpRequestProcessor.java @@ -24,6 +24,8 @@ package com.questdb.cutlass.http; import com.questdb.network.IODispatcher; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; public interface HttpRequestProcessor { void onHeadersReady(HttpConnectionContext context); diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpResponseHeader.java b/core/src/main/java/com/questdb/cutlass/http/HttpResponseHeader.java index 600a4ee5c..260f6e827 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpResponseHeader.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpResponseHeader.java @@ -23,6 +23,8 @@ package com.questdb.cutlass.http; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.str.CharSink; public interface HttpResponseHeader extends CharSink { diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpResponseSink.java b/core/src/main/java/com/questdb/cutlass/http/HttpResponseSink.java index b2b598e36..981cb5622 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpResponseSink.java +++ b/core/src/main/java/com/questdb/cutlass/http/HttpResponseSink.java @@ -26,6 +26,8 @@ package com.questdb.cutlass.http; import com.questdb.log.Log; import com.questdb.log.LogFactory; import com.questdb.network.NetworkFacade; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.*; import com.questdb.std.ex.ZLibException; import com.questdb.std.str.AbstractCharSink; diff --git a/core/src/main/java/com/questdb/cutlass/http/processors/JsonQueryProcessor.java b/core/src/main/java/com/questdb/cutlass/http/processors/JsonQueryProcessor.java index c91ed6a1b..0fb0114f1 100644 --- a/core/src/main/java/com/questdb/cutlass/http/processors/JsonQueryProcessor.java +++ b/core/src/main/java/com/questdb/cutlass/http/processors/JsonQueryProcessor.java @@ -37,6 +37,8 @@ import com.questdb.log.LogFactory; import com.questdb.log.LogRecord; import com.questdb.network.IODispatcher; import com.questdb.network.IOOperation; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.*; import com.questdb.std.str.CharSink; diff --git a/core/src/main/java/com/questdb/cutlass/http/processors/StaticContentProcessor.java b/core/src/main/java/com/questdb/cutlass/http/processors/StaticContentProcessor.java index f27808b42..52476d463 100644 --- a/core/src/main/java/com/questdb/cutlass/http/processors/StaticContentProcessor.java +++ b/core/src/main/java/com/questdb/cutlass/http/processors/StaticContentProcessor.java @@ -28,6 +28,8 @@ import com.questdb.log.Log; import com.questdb.log.LogFactory; import com.questdb.network.IODispatcher; import com.questdb.network.IOOperation; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.*; import com.questdb.std.str.FileNameExtractorCharSequence; import com.questdb.std.str.LPSZ; diff --git a/core/src/main/java/com/questdb/cutlass/http/processors/TableStatusCheckProcessor.java b/core/src/main/java/com/questdb/cutlass/http/processors/TableStatusCheckProcessor.java index ac582e36f..0151fa13f 100644 --- a/core/src/main/java/com/questdb/cutlass/http/processors/TableStatusCheckProcessor.java +++ b/core/src/main/java/com/questdb/cutlass/http/processors/TableStatusCheckProcessor.java @@ -25,9 +25,13 @@ package com.questdb.cutlass.http.processors; import com.questdb.cairo.CairoEngine; import com.questdb.cairo.TableUtils; -import com.questdb.cutlass.http.*; +import com.questdb.cutlass.http.HttpChunkedResponseSocket; +import com.questdb.cutlass.http.HttpConnectionContext; +import com.questdb.cutlass.http.HttpRequestProcessor; import com.questdb.network.IODispatcher; import com.questdb.network.IOOperation; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.Chars; import com.questdb.std.Misc; import com.questdb.std.str.Path; diff --git a/core/src/main/java/com/questdb/cutlass/http/processors/TextImportProcessor.java b/core/src/main/java/com/questdb/cutlass/http/processors/TextImportProcessor.java index db2134488..87f73102f 100644 --- a/core/src/main/java/com/questdb/cutlass/http/processors/TextImportProcessor.java +++ b/core/src/main/java/com/questdb/cutlass/http/processors/TextImportProcessor.java @@ -34,6 +34,8 @@ import com.questdb.log.Log; import com.questdb.log.LogFactory; import com.questdb.network.IODispatcher; import com.questdb.network.IOOperation; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; import com.questdb.std.*; import com.questdb.std.str.CharSink; diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/WireParser.java b/core/src/main/java/com/questdb/cutlass/pgwire/WireParser.java new file mode 100644 index 000000000..5323155ca --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/WireParser.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire; + +import com.questdb.cutlass.pgwire.codecs.AbstractTypePrefixedHeader; +import com.questdb.cutlass.pgwire.codecs.in.StartupMessage; +import com.questdb.cutlass.pgwire.codecs.out.AuthenticationMsg; +import com.questdb.cutlass.pgwire.codecs.out.ParameterStatusMsg; +import com.questdb.cutlass.pgwire.codecs.out.ReadyForQueryMsg; +import com.questdb.log.Log; +import com.questdb.log.LogFactory; +import com.questdb.network.Net; +import com.questdb.network.NetworkFacade; +import com.questdb.network.PeerDisconnectedException; +import com.questdb.network.PeerIsSlowToReadException; +import com.questdb.std.Unsafe; +import com.questdb.std.str.DirectByteCharSequence; + +import java.io.Closeable; + +public class WireParser implements Closeable { + + private final static Log LOG = LogFactory.getLog(WireParser.class); + + private int state = 0; + private final NetworkFacade nf; + private final long recvBuffer; + private final long sendBuffer; + private final int recvBufferSize; + private final int sendBufferSize; + private long recvBufferOffset = 0; + + public WireParser(WireParserConfiguration configuration) { + this.nf = configuration.getNetworkFacade(); + this.recvBufferSize = configuration.getRecvBufferSize(); + this.recvBuffer = Unsafe.malloc(this.recvBufferSize); + this.sendBufferSize = configuration.getSendBufferSize(); + this.sendBuffer = Unsafe.malloc(this.sendBufferSize); + } + + @Override + public void close() { + Unsafe.free(sendBuffer, sendBufferSize); + Unsafe.free(recvBuffer, recvBufferSize); + } + + public void recv(long fd) throws PeerDisconnectedException, PeerIsSlowToReadException { + final int remaining = (int) (recvBufferSize - recvBufferOffset); + + if (remaining < 1) { + throw new RuntimeException("buffer overflow"); + } + + final int n = Net.recv(fd, recvBuffer + recvBufferOffset, remaining); + if (n == -1) { + throw PeerDisconnectedException.INSTANCE; + } + + if (n == 0) { + // todo: stay in tight loop for a bit before giving up + // todo: this exception is misplaced - peer is writing here + throw PeerIsSlowToReadException.INSTANCE; + } + + if (parse(fd, recvBuffer, n)) { + recvBufferOffset = 0; + } else { + recvBufferOffset += n; + } + } + + private void disconnectClient(long fd) { + nf.close(fd); + } + + private void executeParseAndSendResult(long fd, CharSequence query) { + long offset; + // send 'ParseComplete' + AuthenticationMsg.setType(sendBuffer, (byte) '1'); + AuthenticationMsg.setLen(sendBuffer, 4); + + offset = 5; +// Net.send(clientFd, sendBuffer, 5); + + // send 'ReadyForQuery' + ReadyForQueryMsg.setType(sendBuffer + offset, (byte) 'Z'); + ReadyForQueryMsg.setLen(sendBuffer + offset, 5); + ReadyForQueryMsg.setStatus(sendBuffer + offset, (byte) 'I'); + Net.send(fd, sendBuffer, (int) (offset + 6)); + + } + + /** + * returns address of where parsing stopped. If there are remaining bytes left + * int the buffer they need to be passed again in parse function along with + * any additional bytes received + * + * @param address + * @param len + * @return + */ + private boolean parse(long fd, long address, int len) { + long limit = address + len; + int msgLen; + long msgLimit; + + final int remaining = (int) (limit - address); + + switch (state) { + case 0: + // expect startup request + if (remaining < 4) { + return false; + } + + // there is data for length + // this is quite specific to message type :( + msgLen = StartupMessage.getLen(address); // postgesql includes length bytes in length of message + + // do we have the rest of the message? + if (msgLen > remaining) { + // we have length - get the rest when ready + return false; + } + + // 'StartupMessage' + + // consume message + // process protocol + int protocol = StartupMessage.getProtocol(address); + // todo: validate protocol, see 'NegotiateProtocolVersion' + + // extract properties + msgLimit = address + msgLen; + long lo = address + 8; // 8 is offset where name value pairs begin + // there is an extra byte at the end and it has to be 0 + while (lo < msgLimit - 1) { + + long hi = getStringLength(lo, msgLimit); + + // todo: close connection when protocol is broken + assert hi > -1; + CharSequence name = new DirectByteCharSequence().of(lo, hi); + + // name is ready + + lo = hi + 1; + + hi = getStringLength(lo, msgLimit); + assert hi > -1; + CharSequence value = new DirectByteCharSequence().of(lo, hi); + + lo = hi + 1; + + LOG.info() + .$("protocol [major=").$(protocol >> 16) + .$(", minor=").$((short) protocol) + .$(", name=").$(name) + .$(", value=").$(value) + .$(']').$(); + } + + // todo: close connection if protocol is violated + assert Unsafe.getUnsafe().getByte(lo) == 0; + + // todo: check that there is no more data sent + assert lo + 1 == limit; + sendClearTextPasswordChallenge(fd); + state = 3; + return true; + case 3: + + // this is a type-prefixed message + // we will wait until we receive the entire header + + if (remaining < AbstractTypePrefixedHeader.LEN) { + // we need to be able to read header and length + return false; + } + + msgLen = AbstractTypePrefixedHeader.getLen(address); + + // msgLen does not take into account type byte + if (msgLen > remaining - 1) { + return false; + } + + final byte type = AbstractTypePrefixedHeader.getType(address); + + LOG.info().$("got msg '").$((char) type).$('\'').$(); + + msgLimit = address + msgLen + 1; + lo = address + AbstractTypePrefixedHeader.LEN; // 8 is offset where name value pairs begin + + switch (type) { + case 'p': + // +1 is 'type' byte that message length does not account for + long hi = getStringLength(lo, msgLimit); + + CharSequence password = new DirectByteCharSequence().of(lo, hi); + + LOG.info().$("password=").$(password).$(); + + // todo: check that this is all client sent + assert limit == msgLimit; + + // send login ok + sendLoginOk(fd); + break; + case 'P': + + // 'Parse' + // this appears to be the execution side - we must at least return 'RowDescription' + // possibly more, check QueryExecutionImpl.processResults() in PG driver for more info + + hi = getStringLength(lo, msgLimit); + + CharSequence preparedStatementName = new DirectByteCharSequence().of(lo, hi); + LOG.info().$("prepared statement name: ").$(preparedStatementName).$(); + + lo = hi + 1; + hi = getStringLength(lo, msgLimit); + CharSequence query = new DirectByteCharSequence().of(lo, hi); + LOG.info().$("query: ").$(query).$(); + + // todo: read parameter information + + executeParseAndSendResult(fd, query); + break; + + case 'X': + + // 'Terminate' + disconnectClient(fd); + state = 0; + break; + + } + } + return true; + } + + private void sendClearTextPasswordChallenge(long fd) { + AuthenticationMsg.setType(sendBuffer, (byte) 'R'); + AuthenticationMsg.setLen(sendBuffer, 8); + AuthenticationMsg.setResponseCode(sendBuffer, 3); + Net.send(fd, sendBuffer, 9); + // todo: deal with incomplete send + } + + private void sendLoginOk(long fd) { + // send login ok + // send authentication challenge + AuthenticationMsg.setType(sendBuffer, (byte) 'R'); + AuthenticationMsg.setLen(sendBuffer, 8); + AuthenticationMsg.setResponseCode(sendBuffer, 0); +// Net.send(clientFd, sendBuffer, 9); + // length so far 9 + + // send 'ParameterStatus' + long offset = 9; + offset += ParameterStatusMsg.setParameterPair( + sendBuffer + offset, + "TimeZone", "GMT"); + + offset += ParameterStatusMsg.setParameterPair( + sendBuffer + offset, + "application_name", "QuestDB"); + + offset += ParameterStatusMsg.setParameterPair( + sendBuffer + offset, + "server_version_num", "100000"); + + offset += ParameterStatusMsg.setParameterPair( + sendBuffer + offset, + "integer_datetimes", "on"); + + // send 'ReadyForQuery' + ReadyForQueryMsg.setType(sendBuffer + offset, (byte) 'Z'); + ReadyForQueryMsg.setLen(sendBuffer + offset, 5); + ReadyForQueryMsg.setStatus(sendBuffer + offset, (byte) 'I'); + nf.send(fd, sendBuffer, (int) (offset + 6)); + } + + private long getStringLength(long x, long limit) { + // calculate length + for (long i = x; i < limit; i++) { + if (Unsafe.getUnsafe().getByte(i) == 0) { + return i; + } + } + return -1; + } + + @FunctionalInterface + public interface MessageHandler { + int onMessage(int action); + } +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/WireParserConfiguration.java b/core/src/main/java/com/questdb/cutlass/pgwire/WireParserConfiguration.java new file mode 100644 index 000000000..079b25c39 --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/WireParserConfiguration.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire; + +import com.questdb.network.NetworkFacade; + +public interface WireParserConfiguration { + NetworkFacade getNetworkFacade(); + + int getRecvBufferSize(); + + int getSendBufferSize(); +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/AbstractTypePrefixedHeader.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/AbstractTypePrefixedHeader.java new file mode 100644 index 000000000..d9fb4082c --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/AbstractTypePrefixedHeader.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs; + +import com.questdb.std.Unsafe; + +public abstract class AbstractTypePrefixedHeader { + + public static final int LEN = 5; + + public static int getLen(long address) { + return NetworkByteOrderUtils.getInt(address + 1); + } + + public static byte getType(long address) { + return Unsafe.getUnsafe().getByte(address); + } + + public static void setLen(long address, int len) { + NetworkByteOrderUtils.putInt(address + 1, len); + } + + public static void setType(long address, byte type) { + Unsafe.getUnsafe().putByte(address, type); + } +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/Constants.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/Constants.java new file mode 100644 index 000000000..341560865 --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/Constants.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs; + +public final class Constants { + public static final int AUTHENTICATION_OK = 0; + public static final int AUTHENTICATION_KRBv5 = 2; + public static final int AUTHENTICATION_CLEARTEXT_PASSWORD = 3; + public static final int AUTHENTICATION_MD5_PASSWORD = 5; +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/NetworkByteOrderUtils.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/NetworkByteOrderUtils.java new file mode 100644 index 000000000..93cae7efb --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/NetworkByteOrderUtils.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs; + +import com.questdb.std.Unsafe; + +public class NetworkByteOrderUtils { + public static int getInt(long address) { + int b = Unsafe.getUnsafe().getByte(address); + b = (b << 8) | Unsafe.getUnsafe().getByte(address + 1); + b = (b << 8) | Unsafe.getUnsafe().getByte(address + 2); + return (b << 8) | Unsafe.getUnsafe().getByte(address + 3); + } + + public static void putInt(long address, int value) { + Unsafe.getUnsafe().putByte(address, (byte) (value >>> 24)); + Unsafe.getUnsafe().putByte(address + 1, (byte) (value >>> 16)); + Unsafe.getUnsafe().putByte(address + 2, (byte) (value >>> 8)); + Unsafe.getUnsafe().putByte(address + 3, (byte) (value)); + } +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/QueryRequest.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/QueryRequest.java new file mode 100644 index 000000000..43527d8e6 --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/QueryRequest.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs.in; + +import com.questdb.cutlass.pgwire.codecs.AbstractTypePrefixedHeader; + +public class QueryRequest extends AbstractTypePrefixedHeader { + + +} diff --git a/core/src/main/java/com/questdb/network/DisconnectReason.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/StartupMessage.java similarity index 67% rename from core/src/main/java/com/questdb/network/DisconnectReason.java rename to core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/StartupMessage.java index 416888505..12e7d9418 100644 --- a/core/src/main/java/com/questdb/network/DisconnectReason.java +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/in/StartupMessage.java @@ -21,23 +21,16 @@ * ******************************************************************************/ -package com.questdb.network; +package com.questdb.cutlass.pgwire.codecs.in; -public final class DisconnectReason { - public static final int PEER = 1; - public static final int IDLE = 2; - public static final int SILLY = 3; +import com.questdb.cutlass.pgwire.codecs.NetworkByteOrderUtils; - public static CharSequence nameOf(int disconnectReason) { - switch (disconnectReason) { - case PEER: - return "PEER"; - case IDLE: - return "IDLE"; - case SILLY: - return "SILLY"; - default: - return "UNKNOWN"; - } +public class StartupMessage { + public static int getLen(long address) { + return NetworkByteOrderUtils.getInt(address); + } + + public static int getProtocol(long address) { + return NetworkByteOrderUtils.getInt(address + 4); } } diff --git a/core/src/main/java/com/questdb/cutlass/http/HttpFlowControlException.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMD5PasswordMsg.java similarity index 89% rename from core/src/main/java/com/questdb/cutlass/http/HttpFlowControlException.java rename to core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMD5PasswordMsg.java index b4b49cc58..9f082dfa8 100644 --- a/core/src/main/java/com/questdb/cutlass/http/HttpFlowControlException.java +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMD5PasswordMsg.java @@ -21,7 +21,8 @@ * ******************************************************************************/ -package com.questdb.cutlass.http; +package com.questdb.cutlass.pgwire.codecs.out; + +public class AuthenticationMD5PasswordMsg extends AuthenticationMsg { -public class HttpFlowControlException extends Exception { } diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMsg.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMsg.java new file mode 100644 index 000000000..47b25c67f --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/AuthenticationMsg.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs.out; + +import com.questdb.cutlass.pgwire.codecs.AbstractTypePrefixedHeader; +import com.questdb.cutlass.pgwire.codecs.NetworkByteOrderUtils; + +public class AuthenticationMsg extends AbstractTypePrefixedHeader { + public static void setResponseCode(long address, int status) { + NetworkByteOrderUtils.putInt(address + AbstractTypePrefixedHeader.LEN, status); + } +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ParameterStatusMsg.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ParameterStatusMsg.java new file mode 100644 index 000000000..01d0704d4 --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ParameterStatusMsg.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs.out; + +import com.questdb.cutlass.pgwire.codecs.AbstractTypePrefixedHeader; +import com.questdb.std.Unsafe; + +public class ParameterStatusMsg extends AbstractTypePrefixedHeader { + public static int setParameterPair(long address, CharSequence name, CharSequence value) { + setType(address, (byte) 'S'); + long p = address + AbstractTypePrefixedHeader.LEN; + final long start = p; + p = copyStringZ(p, name); + p = copyStringZ(p, value); + int len = (int) (p - start); + setLen(address, len + AbstractTypePrefixedHeader.LEN - 1); + return len + AbstractTypePrefixedHeader.LEN; + } + + private static long copyStringZ(long p, CharSequence value) { + for (int i = 0, m = value.length(); i < m; i++) { + Unsafe.getUnsafe().putByte(p++, (byte) value.charAt(i)); + } + Unsafe.getUnsafe().putByte(p, (byte) 0); + return p + 1; + } + +} diff --git a/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ReadyForQueryMsg.java b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ReadyForQueryMsg.java new file mode 100644 index 000000000..d98367bac --- /dev/null +++ b/core/src/main/java/com/questdb/cutlass/pgwire/codecs/out/ReadyForQueryMsg.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire.codecs.out; + +import com.questdb.cutlass.pgwire.codecs.AbstractTypePrefixedHeader; +import com.questdb.std.Unsafe; + +public class ReadyForQueryMsg extends AbstractTypePrefixedHeader { + public static void setStatus(long address, byte status) { + Unsafe.getUnsafe().putByte(address + AbstractTypePrefixedHeader.LEN, status); + } +} diff --git a/core/src/main/java/com/questdb/cutlass/http/PeerDisconnectedException.java b/core/src/main/java/com/questdb/network/PeerDisconnectedException.java similarity index 91% rename from core/src/main/java/com/questdb/cutlass/http/PeerDisconnectedException.java rename to core/src/main/java/com/questdb/network/PeerDisconnectedException.java index 8d0380e20..0e2b2ab17 100644 --- a/core/src/main/java/com/questdb/cutlass/http/PeerDisconnectedException.java +++ b/core/src/main/java/com/questdb/network/PeerDisconnectedException.java @@ -21,8 +21,8 @@ * ******************************************************************************/ -package com.questdb.cutlass.http; +package com.questdb.network; -public class PeerDisconnectedException extends HttpFlowControlException { +public class PeerDisconnectedException extends Exception { public static final PeerDisconnectedException INSTANCE = new PeerDisconnectedException(); } diff --git a/core/src/main/java/com/questdb/cutlass/http/PeerIsSlowToReadException.java b/core/src/main/java/com/questdb/network/PeerIsSlowToReadException.java similarity index 91% rename from core/src/main/java/com/questdb/cutlass/http/PeerIsSlowToReadException.java rename to core/src/main/java/com/questdb/network/PeerIsSlowToReadException.java index 56941cbd4..7e6f07778 100644 --- a/core/src/main/java/com/questdb/cutlass/http/PeerIsSlowToReadException.java +++ b/core/src/main/java/com/questdb/network/PeerIsSlowToReadException.java @@ -21,8 +21,8 @@ * ******************************************************************************/ -package com.questdb.cutlass.http; +package com.questdb.network; -public class PeerIsSlowToReadException extends HttpFlowControlException { +public class PeerIsSlowToReadException extends Exception { public static final PeerIsSlowToReadException INSTANCE = new PeerIsSlowToReadException(); } diff --git a/core/src/test/java/com/questdb/cutlass/http/IODispatcherTest.java b/core/src/test/java/com/questdb/cutlass/http/IODispatcherTest.java index e52a62bfe..b623ebd41 100644 --- a/core/src/test/java/com/questdb/cutlass/http/IODispatcherTest.java +++ b/core/src/test/java/com/questdb/cutlass/http/IODispatcherTest.java @@ -44,9 +44,8 @@ import com.questdb.std.str.StringSink; import com.questdb.std.time.MillisecondClock; import com.questdb.test.tools.TestUtils; import org.jetbrains.annotations.NotNull; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; +import org.junit.rules.TemporaryFolder; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,6 +55,14 @@ import java.util.concurrent.locks.LockSupport; public class IODispatcherTest { private static Log LOG = LogFactory.getLog(IODispatcherTest.class); + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + temp.create(); + } + @Test public void testBiasWrite() throws Exception { @@ -239,7 +246,7 @@ public class IODispatcherTest { @Test public void testImportMultipleOnSameConnection() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try (CairoEngine engine = new CairoEngine(new DefaultCairoConfiguration(baseDir)); @@ -402,7 +409,7 @@ public class IODispatcherTest { @Test public void testImportMultipleOnSameConnectionFragmented() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try (CairoEngine engine = new CairoEngine(new DefaultCairoConfiguration(baseDir)); @@ -540,7 +547,7 @@ public class IODispatcherTest { @Test public void testImportMultipleOnSameConnectionSlow() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try ( @@ -687,7 +694,7 @@ public class IODispatcherTest { @Test public void testJsonQuery() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try ( @@ -769,7 +776,7 @@ public class IODispatcherTest { TestUtils.assertMemoryLeak(() -> { final NetworkFacade nf = NetworkFacadeImpl.INSTANCE; - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(nf, baseDir, 128); try (CairoEngine engine = new CairoEngine(new DefaultCairoConfiguration(baseDir)); @@ -912,7 +919,7 @@ public class IODispatcherTest { @Test public void testJsonQuerySyntaxError() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try ( @@ -1087,7 +1094,7 @@ public class IODispatcherTest { @Test public void testSCPConnectDownloadDisconnect() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try (HttpServer httpServer = new HttpServer(httpConfiguration)) { @@ -1266,7 +1273,7 @@ public class IODispatcherTest { @Test public void testSCPFullDownload() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); try (HttpServer httpServer = new HttpServer(httpConfiguration)) { @@ -2097,7 +2104,7 @@ public class IODispatcherTest { @Ignore public void testUpload() throws Exception { TestUtils.assertMemoryLeak(() -> { - final String baseDir = System.getProperty("java.io.tmpdir"); + final String baseDir = temp.getRoot().getAbsolutePath(); // final String baseDir = "/home/vlad/dev/123"; final DefaultHttpServerConfiguration httpConfiguration = createHttpServerConfiguration(baseDir); diff --git a/core/src/test/java/com/questdb/cutlass/pgwire/WireParserTest.java b/core/src/test/java/com/questdb/cutlass/pgwire/WireParserTest.java new file mode 100644 index 000000000..83af33cd3 --- /dev/null +++ b/core/src/test/java/com/questdb/cutlass/pgwire/WireParserTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (C) 2014-2019 Appsicle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +package com.questdb.cutlass.pgwire; + +import com.questdb.network.*; +import com.questdb.std.Os; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +public class WireParserTest { + + @Test + public void testSimple() throws SQLException { + // start simple server + + long fd = Net.socketTcp(true); + + WireParser wireParser = new WireParser(new WireParserConfiguration() { + @Override + public NetworkFacade getNetworkFacade() { + return NetworkFacadeImpl.INSTANCE; + } + + @Override + public int getRecvBufferSize() { + return 1024 * 1024; + } + + @Override + public int getSendBufferSize() { + return 1024 * 1024; + } + }); + + + Net.setReusePort(fd); + + if (Net.bindTcp(fd, 0, 9120)) { + Net.listen(fd, 128); + + new Thread(() -> { + final long clientFd = Net.accept(fd); + while (true) { + try { + wireParser.recv(clientFd); + } catch (PeerDisconnectedException e) { + break; + } catch (PeerIsSlowToReadException ignored) { + } + } + }).start(); + + Properties properties = new Properties(); + properties.setProperty("user", "xyz"); + properties.setProperty("password", "oh"); + properties.setProperty("sslmode", "disable"); + + final Connection connection = DriverManager.getConnection("jdbc:postgresql://127.0.0.1:9120/nabu_app", properties); +// Statement statement = connection.createStatement(); +// statement.executeQuery("select * from tab"); + connection.close(); + + } else { + throw NetworkError.instance(Os.errno()).couldNotBindSocket(); + } + + } +} \ No newline at end of file -- GitLab