Package org.jruby.pg

Source Code of org.jruby.pg.Connection

package org.jruby.pg;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.jcodings.Encoding;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyEncoding;
import org.jruby.RubyException;
import org.jruby.RubyFixnum;
import org.jruby.RubyFloat;
import org.jruby.RubyHash;
import org.jruby.RubyIO;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.pg.internal.ConnectionState;
import org.jruby.pg.internal.LargeObjectAPI;
import org.jruby.pg.internal.PostgresqlConnection;
import org.jruby.pg.internal.PostgresqlException;
import org.jruby.pg.internal.PostgresqlString;
import org.jruby.pg.internal.ResultSet;
import org.jruby.pg.internal.Value;
import org.jruby.pg.internal.messages.CopyData;
import org.jruby.pg.internal.messages.Format;
import org.jruby.pg.internal.messages.NotificationResponse;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

public class Connection extends RubyObject {
  private final static String[]            POSITIONAL_ARGS                = {
      "host", "port", "options", "tty", "dbname", "user", "password"     };
    private final static Map<String, String> postgresEncodingToRubyEncoding = new HashMap<String, String>();
    private final static Map<String, String> rubyEncodingToPostgresEncoding = new HashMap<String, String>();
    protected final static int FORMAT_TEXT = 0;
    protected final static int FORMAT_BINARY = 1;

    protected static Connection LAST_CONNECTION = null;

    protected PostgresqlConnection postgresqlConnection;

    protected PostgresqlString BEGIN_QUERY = new PostgresqlString("BEGIN");
    protected PostgresqlString COMMIT_QUERY = new PostgresqlString("COMMIT");
    protected PostgresqlString ROLLBACK_QUERY = new PostgresqlString("ROLLBACK");
    private Properties props;

    static {
      postgresEncodingToRubyEncoding.put("BIG5",          "Big5"        );
      postgresEncodingToRubyEncoding.put("EUC_CN",        "GB2312"      );
      postgresEncodingToRubyEncoding.put("EUC_JP",        "EUC-JP"      );
      postgresEncodingToRubyEncoding.put("EUC_JIS_2004""EUC-JP"      );
      postgresEncodingToRubyEncoding.put("EUC_KR",        "EUC-KR"      );
      postgresEncodingToRubyEncoding.put("EUC_TW",        "EUC-TW"      );
      postgresEncodingToRubyEncoding.put("GB18030",       "GB18030"     );
      postgresEncodingToRubyEncoding.put("GBK",           "GBK"         );
      postgresEncodingToRubyEncoding.put("ISO_8859_5",    "ISO-8859-5"  );
      postgresEncodingToRubyEncoding.put("ISO_8859_6",    "ISO-8859-6"  );
      postgresEncodingToRubyEncoding.put("ISO_8859_7",    "ISO-8859-7"  );
      postgresEncodingToRubyEncoding.put("ISO_8859_8",    "ISO-8859-8"  );
      postgresEncodingToRubyEncoding.put("KOI8",          "KOI8-R"      );
      postgresEncodingToRubyEncoding.put("KOI8R",         "KOI8-R"      );
      postgresEncodingToRubyEncoding.put("KOI8U",         "KOI8-U"      );
      postgresEncodingToRubyEncoding.put("LATIN1",        "ISO-8859-1"  );
      postgresEncodingToRubyEncoding.put("LATIN2",        "ISO-8859-2"  );
      postgresEncodingToRubyEncoding.put("LATIN3",        "ISO-8859-3"  );
      postgresEncodingToRubyEncoding.put("LATIN4",        "ISO-8859-4"  );
      postgresEncodingToRubyEncoding.put("LATIN5",        "ISO-8859-9"  );
      postgresEncodingToRubyEncoding.put("LATIN6",        "ISO-8859-10" );
      postgresEncodingToRubyEncoding.put("LATIN7",        "ISO-8859-13" );
      postgresEncodingToRubyEncoding.put("LATIN8",        "ISO-8859-14" );
      postgresEncodingToRubyEncoding.put("LATIN9",        "ISO-8859-15" );
      postgresEncodingToRubyEncoding.put("LATIN10",       "ISO-8859-16" );
      postgresEncodingToRubyEncoding.put("MULE_INTERNAL", "Emacs-Mule"  );
      postgresEncodingToRubyEncoding.put("SJIS",          "Windows-31J" );
      postgresEncodingToRubyEncoding.put("SHIFT_JIS_2004","Windows-31J" );
      postgresEncodingToRubyEncoding.put("UHC",           "CP949"       );
      postgresEncodingToRubyEncoding.put("UTF8",          "UTF-8"       );
      postgresEncodingToRubyEncoding.put("WIN866",        "IBM866"      );
      postgresEncodingToRubyEncoding.put("WIN874",        "Windows-874" );
      postgresEncodingToRubyEncoding.put("WIN1250",       "Windows-1250");
      postgresEncodingToRubyEncoding.put("WIN1251",       "Windows-1251");
      postgresEncodingToRubyEncoding.put("WIN1252",       "Windows-1252");
      postgresEncodingToRubyEncoding.put("WIN1253",       "Windows-1253");
      postgresEncodingToRubyEncoding.put("WIN1254",       "Windows-1254");
      postgresEncodingToRubyEncoding.put("WIN1255",       "Windows-1255");
      postgresEncodingToRubyEncoding.put("WIN1256",       "Windows-1256");
      postgresEncodingToRubyEncoding.put("WIN1257",       "Windows-1257");
      postgresEncodingToRubyEncoding.put("WIN1258",       "Windows-1258");

      // set the mapping from ruby encoding to postgresql encoding
      rubyEncodingToPostgresEncoding.put("Big5",          "BIG5"          );
      rubyEncodingToPostgresEncoding.put("GB2312",        "EUC_CN"        );
      rubyEncodingToPostgresEncoding.put("EUC-JP",        "EUC_JP"        );
      rubyEncodingToPostgresEncoding.put("EUC-KR",        "EUC_KR"        );
      rubyEncodingToPostgresEncoding.put("EUC-TW",        "EUC_TW"        );
      rubyEncodingToPostgresEncoding.put("GB18030",       "GB18030"       );
      rubyEncodingToPostgresEncoding.put("GBK",           "GBK"           );
      rubyEncodingToPostgresEncoding.put("KOI8-R",        "KOI8"          );
      rubyEncodingToPostgresEncoding.put("KOI8-U",        "KOI8U"         );
      rubyEncodingToPostgresEncoding.put("ISO-8859-1",    "LATIN1"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-2",    "LATIN2"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-3",    "LATIN3"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-4",    "LATIN4"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-5",    "ISO_8859_5"    );
      rubyEncodingToPostgresEncoding.put("ISO-8859-6",    "ISO_8859_6"    );
      rubyEncodingToPostgresEncoding.put("ISO-8859-7",    "ISO_8859_7"    );
      rubyEncodingToPostgresEncoding.put("ISO-8859-8",    "ISO_8859_8"    );
      rubyEncodingToPostgresEncoding.put("ISO-8859-9",    "LATIN5"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-10",   "LATIN6"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-13",   "LATIN7"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-14",   "LATIN8"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-15",   "LATIN9"        );
      rubyEncodingToPostgresEncoding.put("ISO-8859-16",   "LATIN10"       );
      rubyEncodingToPostgresEncoding.put("Emacs-Mule",    "MULE_INTERNAL" );
      rubyEncodingToPostgresEncoding.put("Windows-31J",   "SJIS"          );
      rubyEncodingToPostgresEncoding.put("Windows-31J",   "SHIFT_JIS_2004");
      rubyEncodingToPostgresEncoding.put("UHC",           "CP949"         );
      rubyEncodingToPostgresEncoding.put("UTF-8",         "UTF8"          );
      rubyEncodingToPostgresEncoding.put("IBM866",        "WIN866"        );
      rubyEncodingToPostgresEncoding.put("Windows-874",   "WIN874"        );
      rubyEncodingToPostgresEncoding.put("Windows-1250""WIN1250"       );
      rubyEncodingToPostgresEncoding.put("Windows-1251""WIN1251"       );
      rubyEncodingToPostgresEncoding.put("Windows-1252""WIN1252"       );
      rubyEncodingToPostgresEncoding.put("Windows-1253""WIN1253"       );
      rubyEncodingToPostgresEncoding.put("Windows-1254""WIN1254"       );
      rubyEncodingToPostgresEncoding.put("Windows-1255""WIN1255"       );
      rubyEncodingToPostgresEncoding.put("Windows-1256""WIN1256"       );
      rubyEncodingToPostgresEncoding.put("Windows-1257""WIN1257"       );
      rubyEncodingToPostgresEncoding.put("Windows-1258""WIN1258"       );
    }

    public Connection(Ruby ruby, RubyClass rubyClass) {
        super(ruby, rubyClass);
    }

    public static void define(Ruby ruby, RubyModule pg, RubyModule constants) {
        RubyClass connection = pg.defineClassUnder("Connection", ruby.getObject(), CONNECTION_ALLOCATOR);

        connection.includeModule(constants);

        connection.defineAnnotatedMethods(Connection.class);

        connection.getSingletonClass().defineAlias("connect", "new");
        connection.getSingletonClass().defineAlias("open", "new");
        connection.getSingletonClass().defineAlias("setdb", "new");
        connection.getSingletonClass().defineAlias("setdblogin", "new");
    }

    /******     PG::Connection CLASS METHODS     ******/

    @JRubyMethod(alias = {"escape, escape_string"}, meta = true)
    public static IRubyObject escape_literal_native(ThreadContext context, IRubyObject self, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod(meta = true, required = 1, argTypes = {RubyArray.class})
    public static IRubyObject escape_bytea(ThreadContext context, IRubyObject self, IRubyObject array) {
      if (LAST_CONNECTION != null)
        return LAST_CONNECTION.escape_bytea(context, array);
      return escapeBytes(context, array, context.nil, false);
    }

    @JRubyMethod(meta = true)
    public static IRubyObject unescape_bytea(ThreadContext context, IRubyObject self, IRubyObject array) {
      return unescapeBytes(context, array);
    }

    @JRubyMethod(meta = true, required = 2, argTypes = {RubyString.class, RubyString.class})
    public static IRubyObject encrypt_password(ThreadContext context, IRubyObject self, IRubyObject password, IRubyObject username) {
      if (username.isNil() || password.isNil())
        throw context.runtime.newTypeError("usernamd ane password cannot be nil");

      try {
        byte[] cryptedPassword = PostgresqlConnection.encrypt(((RubyString) username).getBytes(), ((RubyString) password).getBytes());
        return context.runtime.newString(new ByteList(cryptedPassword));
      } catch (Exception e) {
        throw context.runtime.newRuntimeError(e.getLocalizedMessage());
      }
    }

    @JRubyMethod(meta = true)
    public static IRubyObject quote_ident(ThreadContext context, IRubyObject self, IRubyObject identifier) {
      return quoteIdentifier(context, identifier);
    }

    @JRubyMethod(rest = true, meta = true)
    public static IRubyObject connect_start(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) {
      Connection connection = new Connection(context.runtime, context.runtime.getModule("PG").getClass("Connection"));
      connection.props = parse_args(context, args);
      return connection.connectStart(context, block);
    }

    @JRubyMethod(meta = true)
    public static IRubyObject reset_last_conn(ThreadContext context, IRubyObject self) {
      LAST_CONNECTION = null;
      return context.nil;
    }

    @JRubyMethod(rest = true, meta = true)
    public static IRubyObject ping(ThreadContext context, IRubyObject self, IRubyObject[] args) {
        return context.nil;
    }

    @JRubyMethod(meta = true)
    public static IRubyObject connectdefaults(ThreadContext context, IRubyObject self) {
        return context.nil;
    }

    /**
     * binary data is received from the jdbc driver after being unescaped
     *
     * @param context
     * @param _array
     * @return
     */
    public static IRubyObject unescapeBytes (ThreadContext context, IRubyObject _array) {
      RubyString string = (RubyString) _array;
      byte[] bytes = string.getBytes();
      if (bytes[0] == '\\' && bytes[1] == 'x') {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (int i = 2; i < bytes.length; i += 2) {
          int b = charToInt(bytes[i]) * 16 + charToInt(bytes[i + 1]);
          out.write(b);
        }
        return context.runtime.newString(new ByteList(out.toByteArray()));
      } else {
        return _array;
      }
    }

    private IRubyObject connectStart(ThreadContext context, Block block) {
      try {
        postgresqlConnection = PostgresqlConnection.connectStart(props);
        if (block.isGiven()) {
          IRubyObject value = block.yield(context, this);
          finish(context);
          return value;
        }
        return this;
      } catch (Exception e) {
        throw context.runtime.newIOError(e.getLocalizedMessage());
      }
    }

    private static int charToInt(byte b) {
      if (Character.isLetter(b))
        return Character.toUpperCase(b) - 'A' + 10;
      else
        return b - '0';
    }

    private static IRubyObject quoteIdentifier(ThreadContext context, IRubyObject _identifier) {
      RubyString identifier = (RubyString) _identifier;
      byte[] bytes = identifier.getBytes();
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      out.write('"');
      for (int i = 0; i < bytes.length; i++) {
        if (bytes[i] == '"')
          out.write('"');
        out.write(bytes[i]);
      }
      out.write('"');
      byte[] newBytes = out.toByteArray();
      return context.runtime.newString(new ByteList(newBytes));
    }

    private static IRubyObject escapeBytes(ThreadContext context, IRubyObject _array, IRubyObject encoding, boolean standardConforminStrings) {
      RubyString array = (RubyString) _array;
      byte[] bytes = array.getBytes();

      return escapeBytes(context, bytes, encoding, standardConforminStrings);
    }

    private static IRubyObject escapeBytes(ThreadContext context, byte[] bytes, IRubyObject encoding, boolean standardConformingStrings) {
      return escapeBytes(context, bytes, 0, bytes.length, encoding, standardConformingStrings);
    }

    private static IRubyObject escapeBytes(ThreadContext context, byte[] bytes, int offset, int len,
        IRubyObject encoding, boolean standardConformingStrings) {
      if (len < 0 || offset < 0 || offset + len > bytes.length) {
        throw context.runtime.newArgumentError("Oops array offset or length isn't correct");
      }

      String prefix = "";
      if (!standardConformingStrings)
        prefix = "\\";

      ByteArrayOutputStream out = new ByteArrayOutputStream();
      PrintWriter writer = new PrintWriter(out);
      for (int i = offset; i < (offset + len); i++) {
        int byteValue= bytes[i] & 0xFF;
        if (byteValue == 39) {
          // escape the single quote
          writer.append(prefix).append("\\047");
        } else if (byteValue == 92) {
          // escape the backslash
          writer.append(prefix).append("\\134");
        } else if (byteValue >= 0 && byteValue <= 31 || byteValue >= 127 && byteValue <= 255) {
          writer.append(prefix).printf("\\%03o", byteValue);
        } else {
          // all other characters, print as themselves
          writer.write(byteValue);
        }
      }

      writer.close();
      byte[] outBytes = out.toByteArray();
      if (encoding.isNil())
        return context.runtime.newString(new ByteList(outBytes));
      return context.runtime.newString(new ByteList(outBytes, ((RubyEncoding) encoding).getEncoding()));
    }

    @SuppressWarnings("unchecked")
    private static Properties parse_args(ThreadContext context, IRubyObject[] args) {
      Properties argumentsHash = new Properties();
      if (args.length == 0)
        return argumentsHash;
      if (args.length > 7)
        throw context.getRuntime().newArgumentError("extra positional parameter");
    if (args.length != 7 && args.length != 1)
      throw context.getRuntime().newArgumentError(
          "Wrong number of arguments, see the documentation");


      if (args.length == 1) {
        // we have a string or hash
        if (args[0] instanceof RubyHash) {
          RubyHash hash = (RubyHash)args[0];

          for (Object _entry : hash.entrySet()) {
            Entry<String, Object> entry = (Entry<String, Object>) _entry;
            argumentsHash.put(PostgresHelpers.stringify(entry.getKey()), PostgresHelpers.stringify(entry.getValue()));
          }
        } else if (args[0] instanceof RubyString) {
        String[] tokens = tokenizeString(args[0].asJavaString());
        if (tokens.length % 2 != 0)
          throw context.runtime.newArgumentError("wrong connection string");
        for (int i = 0; i < tokens.length; i += 2)
          argumentsHash.put(tokens[i], tokens[i + 1]);
        } else {
          throw context.runtime.newArgumentError("Wrong type/number of arguments, see the documentation");
        }
      } else {
        // we have positional parameters
        for (int i = 0 ; i < POSITIONAL_ARGS.length ; i++) {
          if (!args[i].isNil())
          argumentsHash.put(POSITIONAL_ARGS[i], ((RubyObject) args[i]).to_s()
              .asJavaString());
        }
      }
      return argumentsHash;
    }

    private static ObjectAllocator CONNECTION_ALLOCATOR = new ObjectAllocator() {
        @Override
        public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
            return new Connection(ruby, rubyClass);
        }
    };

    public RaiseException newPgError(ThreadContext context, String message, ResultSet result, org.jcodings.Encoding encoding) {
      RubyClass klass = context.runtime.getModule("PG").getClass("Error");
      RubyString rubyMessage = context.runtime.newString(message);
      if (encoding != null) {
        RubyEncoding rubyEncoding = RubyEncoding.newEncoding(context.runtime, encoding);
        rubyMessage = (RubyString) rubyMessage.encode(context, rubyEncoding);
      }
      IRubyObject rubyResult = result == null ? context.nil : createResult(context, result, NULL_ARRAY, Block.NULL_BLOCK);
      IRubyObject exception = klass.newInstance(context, rubyMessage, rubyResult, Block.NULL_BLOCK);
      return new RaiseException((RubyException) exception);
    }

    /******     PG::Connection INSTANCE METHODS: Connection Control     ******/

    @JRubyMethod(rest = true)
    public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
        props = parse_args(context, args);
        return connectSync(context);
    }

    @JRubyMethod(alias = "reset_poll")
    public IRubyObject connect_poll(ThreadContext context) {
      try {
        ConnectionState state = postgresqlConnection.connectPoll();
        return context.runtime.newFixnum(state.ordinal());
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"finish", "close"})
    public IRubyObject finish(ThreadContext context) {
      try {
        if (postgresqlConnection.closed())
          throw newPgError(context, "connection is closed", null, getClientEncodingAsJavaEncoding(context));
        postgresqlConnection.close();
        return context.nil;
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject status(ThreadContext context) {
      return context.runtime.newFixnum(postgresqlConnection.status().ordinal());
    }

    @JRubyMethod(name = "finished?")
    public IRubyObject finished_p(ThreadContext context) {
        return postgresqlConnection.closed() ? context.runtime.getTrue() : context.runtime.getFalse();
    }

    @JRubyMethod
    public IRubyObject reset(ThreadContext context) {
        finish(context);
        return connectSync(context);
    }

    @JRubyMethod
    public IRubyObject reset_start(ThreadContext context) {
      finish(context);
      return connectStart(context, Block.NULL_BLOCK);
    }

    @JRubyMethod
    public IRubyObject conndefaults(ThreadContext context) {
        return context.nil;
    }

    /******     PG::Connection INSTANCE METHODS: Connection Status     ******/

    @JRubyMethod
    public IRubyObject db(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject user(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject pass(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject host(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject port(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject tty(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject options(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject transaction_status(ThreadContext context) {
      return context.runtime.newFixnum(postgresqlConnection.getTransactionStatus().getValue());
    }

    @JRubyMethod(required = 1)
    public IRubyObject parameter_status(ThreadContext context, IRubyObject arg0) {
      String name = arg0.asJavaString();
      return context.runtime.newString(postgresqlConnection.getParameterStatus(name));
    }

    @JRubyMethod
    public IRubyObject protocol_version(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject server_version(ThreadContext context) {
      return context.runtime.newFixnum(postgresqlConnection.getServerVersion());
    }

    @JRubyMethod
    public IRubyObject error_message(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject socket(ThreadContext context) {
      SelectableChannel socket = postgresqlConnection.getSocket();
      RubyIO rubyIO = RubyIO.newIO(context.runtime, socket);
      return rubyIO.fileno(context);
    }

    @JRubyMethod
    public IRubyObject backend_pid(ThreadContext context) {
      return context.runtime.newFixnum(postgresqlConnection.getBackendPid());
    }

    @JRubyMethod
    public IRubyObject connection_needs_password(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject connection_used_password(ThreadContext context) {
        return context.nil;
    }

    /******     PG::Connection INSTANCE METHODS: Command Execution     ******/

    @JRubyMethod(alias = {"query", "async_exec", "async_query"}, required = 1, optional = 2)
    public IRubyObject exec(ThreadContext context, IRubyObject[] args, Block block) {
        PostgresqlString query = rubyStringAsPostgresqlString(args[0]);
        ResultSet set = null;
        try {
            if (args.length == 1) {
              set = postgresqlConnection.exec(query);
            } else {

              RubyArray params = (RubyArray) args[1];

              Value [] values = new Value[params.getLength()];
              int [] oids = new int[params.getLength()];
              fillValuesAndFormat(context, params, values, oids);
              Format resultFormat = getFormat(context, args);
              set = postgresqlConnection.execQueryParams(query, values, resultFormat, oids);
            }

            if (set == null)
              return context.nil;
        } catch (PostgresqlException e) {
          throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
        } catch (Exception sqle) {
            throw newPgError(context, sqle.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
        }

        return createResult(context, set, args, block);
    }

    private IRubyObject connectSync(ThreadContext context) {
        // to make testing possible
        if (System.getenv("PG_TEST_SSL") != null) {
          props.setProperty("ssl", "require");
        }

        try {
            // connection = (BaseConnection)driver.connect(connectionString, props);
            postgresqlConnection = PostgresqlConnection.connectDb(props);
            // set the encoding if the default internal_encoding is set
            set_default_encoding(context);

            LAST_CONNECTION = this;
        } catch (Exception e) {
            throw newPgError(context, e.getLocalizedMessage(), null, null);
        }
        return context.nil;
    }

    private Format getFormat(ThreadContext context, IRubyObject [] args) {
      Format resultFormat = Format.Text;
      if (args.length == 3)
        resultFormat = ((RubyFixnum) args[2]).getLongValue() == 1 ? Format.Binary : Format.Text;
      return resultFormat;
    }

    private void fillValuesAndFormat(ThreadContext context, RubyArray params, Value[] values, int [] oids) {
      RubySymbol value_s = context.runtime.newSymbol("value");
      RubySymbol type_s = context.runtime.newSymbol("type");
      RubySymbol format_s = context.runtime.newSymbol("format");
      for (int i = 0; i < params.getLength(); i++) {
        IRubyObject param = params.entry(i);
        Format valueFormat = Format.Text;
        if (param.isNil()) {
          values[i] = new Value(null, valueFormat);
        } else if (param instanceof RubyHash) {
          RubyHash hash = (RubyHash) params.get(i);
          IRubyObject value = hash.op_aref(context, value_s);
          IRubyObject type = hash.op_aref(context, type_s);
          IRubyObject format = hash.op_aref(context, format_s);
          if (!type.isNil())
            oids[i] = (int) ((RubyFixnum) type).getLongValue();
          if (!format.isNil())
            valueFormat = ((RubyFixnum) format).getLongValue() == 1 ? Format.Binary : Format.Text;
          if (value.isNil())
            values[i] = new Value(null, valueFormat);
          else
            values[i] = new Value(((RubyString) value).getBytes(), valueFormat);
        } else {
          RubyString rubyString;
          if (param instanceof RubyString)
            rubyString = (RubyString) param;
          else
            rubyString = (RubyString) ((RubyObject) param).to_s();
          values[i] = new Value(rubyString.getBytes(), valueFormat);
        }
      }
    }

    private IRubyObject createResult(ThreadContext context, ResultSet set, IRubyObject [] args, Block block) {
      // by default we return results in text format
      boolean binary = false;
      if (args.length == 3)
        binary = ((RubyFixnum) args[2]).getLongValue() == FORMAT_BINARY;

      Result result = new Result(context.runtime, (RubyClass)context.runtime.getClassFromPath("PG::Result"), this, set, getClientEncodingAsJavaEncoding(context), binary);
      if (block.isGiven())
        return block.call(context, result);
      return result;
    }

    @JRubyMethod(required = 2, rest = true)
    public IRubyObject prepare(ThreadContext context, IRubyObject[] args) {
      try {
        PostgresqlString name = rubyStringAsPostgresqlString(args[0]);
        PostgresqlString query = rubyStringAsPostgresqlString(args[1]);
        int [] oids = null;
        if (args.length == 3) {
          RubyArray array = ((RubyArray) args[2]);
          oids = new int[array.getLength()];
          for (int i = 0; i < oids.length; i++)
            oids[i] = (int) ((RubyFixnum) array.get(i)).getLongValue();
        }
        oids = oids == null ? new int [0] : oids;
        ResultSet result = postgresqlConnection.prepare(name, query, oids);
        return createResult(context, result, NULL_ARRAY, Block.NULL_BLOCK);
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(required = 1, optional = 2)
    public IRubyObject exec_prepared(ThreadContext context, IRubyObject[] args, Block block) {
      try {
        ResultSet set = execPreparedCommon(context, args, false);
        return createResult(context, set, args, block);
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    private ResultSet execPreparedCommon(ThreadContext context, IRubyObject[] args, boolean async) throws IOException, PostgresqlException {
      PostgresqlString queryName = rubyStringAsPostgresqlString(args[0]);
      Value[] values;
      int[] oids;
      if (args.length > 1) {
        RubyArray array = (RubyArray) args[1];
        values = new Value[array.getLength()];
        oids = new int[array.getLength()];
        fillValuesAndFormat(context, array, values, oids);
      } else {
        values = new Value[0];
        oids = new int[0];
      }
      Format format = getFormat(context, args);
      if (!async)
        return postgresqlConnection.execPrepared(queryName, values, format);
      postgresqlConnection.sendExecPrepared(queryName, values, format);
      return null;
    }

    @JRubyMethod(required = 1)
    public IRubyObject describe_prepared(ThreadContext context, IRubyObject query_name) {
      try {
        PostgresqlString queryName = rubyStringAsPostgresqlString(query_name);
        ResultSet resultSet = postgresqlConnection.describePrepared(queryName);
        return createResult(context, resultSet, NULL_ARRAY, Block.NULL_BLOCK);
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(required = 1)
    public IRubyObject describe_portal(ThreadContext context, IRubyObject arg0) {
      try {
        PostgresqlString name = rubyStringAsPostgresqlString(arg0);
        ResultSet resultSet = postgresqlConnection.describePortal(name);
        return createResult(context, resultSet, NULL_ARRAY, Block.NULL_BLOCK);
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject make_empty_pgresult(ThreadContext context, IRubyObject arg0) {
      return createResult(context, new ResultSet(), NULL_ARRAY, Block.NULL_BLOCK);
    }

    @JRubyMethod(alias = "escape_string")
    public IRubyObject escape(ThreadContext context, IRubyObject _str) {
      RubyString str = (RubyString) _str;
      String javaString = str.toString();
      byte[] bytes = postgresqlConnection.escapeString(javaString).getBytes();
      RubyEncoding encoding = (RubyEncoding) internal_encoding(context);
      return context.runtime.newString(new ByteList(bytes, encoding.getEncoding()));
    }

    @JRubyMethod(required = 1, argTypes = {RubyString.class} )
    public IRubyObject escape_literal_native(ThreadContext context, IRubyObject _str) {
      RubyString str = (RubyString) _str;
      byte[] bytes = str.getBytes();
      int i;
      for (i = 0; i < bytes.length && bytes[i] != '\0'; i++);
      return escapeBytes(context, bytes, 0, i, internal_encoding(context), postgresqlConnection.getStandardConformingStrings());
    }

    @JRubyMethod
    public IRubyObject escape_bytea(ThreadContext context, IRubyObject array) {
      return escapeBytes(context, array, internal_encoding(context), postgresqlConnection.getStandardConformingStrings());
    }

    @JRubyMethod
    public IRubyObject unescape_bytea(ThreadContext context, IRubyObject array) {
      return unescapeBytes(context, array);
    }

    /******     PG::Connection INSTANCE METHODS: Asynchronous Command Processing     ******/

    @JRubyMethod(rest = true)
    public IRubyObject send_query(ThreadContext context, IRubyObject[] args) {
      try {
        if (args.length == 1) {
          PostgresqlString query = rubyStringAsPostgresqlString(args[0]);
          postgresqlConnection.sendQuery(query);
        }
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
      return context.nil;
    }

    @JRubyMethod(rest = true)
    public IRubyObject send_prepare(ThreadContext context, IRubyObject[] args) {
        return context.nil;
    }

    @JRubyMethod(rest = true)
    public IRubyObject send_query_prepared(ThreadContext context, IRubyObject[] args) {
      try {
        execPreparedCommon(context, args, true);
        return context.nil;
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject send_describe_prepared(ThreadContext context, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject send_describe_portal(ThreadContext context, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject get_result(ThreadContext context, Block block) {
      try {
        ResultSet set = postgresqlConnection.getResult();
        return createResult(context, set, NULL_ARRAY, block);
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject consume_input(ThreadContext context) {
      try {
        postgresqlConnection.consumeInput();
        return context.nil;
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject is_busy(ThreadContext context) {
      return context.runtime.newBoolean(postgresqlConnection.isBusy());
    }

    @JRubyMethod
    public IRubyObject set_nonblocking(ThreadContext context, IRubyObject arg0) {
      if (arg0.isTrue())
        postgresqlConnection.setNonBlocking(true);
      postgresqlConnection.setNonBlocking(false);
      return arg0;
    }

    @JRubyMethod(name = {"isnonblocking", "nonblocking?"})
    public IRubyObject isnonblocking(ThreadContext context) {
      return context.runtime.newBoolean(postgresqlConnection.isNonBlocking());
    }

    @JRubyMethod
    public IRubyObject flush(ThreadContext context) {
      try {
        return context.runtime.newBoolean(postgresqlConnection.flush());
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    /******     PG::Connection INSTANCE METHODS: Cancelling Queries in Progress     ******/

    @JRubyMethod
    public IRubyObject cancel(ThreadContext context) {
      try {
        postgresqlConnection.cancel();
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
      return context.nil;
    }

    /******     PG::Connection INSTANCE METHODS: NOTIFY     ******/

    @JRubyMethod
    public IRubyObject notifies(ThreadContext context) {
      NotificationResponse notification = postgresqlConnection.notifications();
      if (notification == null)
        return context.nil;
      RubyHash hash = new RubyHash(context.runtime);

      RubySymbol relname = context.runtime.newSymbol("relname");
      RubySymbol pid = context.runtime.newSymbol("be_pid");
      RubySymbol extra = context.runtime.newSymbol("extra");

      hash.op_aset(context, relname, context.runtime.newString(notification.getCondition()));
      hash.op_aset(context, pid, context.runtime.newFixnum(notification.getPid()));
      hash.op_aset(context, extra, context.runtime.newString(notification.getPayload()));

      return hash;
    }

    /******     PG::Connection INSTANCE METHODS: COPY     ******/

    @JRubyMethod
    public IRubyObject put_copy_data(ThreadContext context, IRubyObject arg0) {
      try {
        byte[] bytes = ((RubyString) arg0).getBytes();
        ByteBuffer data = ByteBuffer.wrap(bytes);
        return context.runtime.newBoolean(postgresqlConnection.putCopyData(data));
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(rest = true)
    public IRubyObject put_copy_end(ThreadContext context, IRubyObject[] args) {
      try {
        return context.runtime.newBoolean(postgresqlConnection.putCopyDone());
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(rest = true)
    public IRubyObject get_copy_data(ThreadContext context, IRubyObject[] args) {
      try {
        boolean async = false;
        if (args.length == 1)
          async = args[0].isTrue();
        CopyData data = postgresqlConnection.getCopyData(async);
        if (data == PostgresqlConnection.COPY_DATA_NOT_READY)
          return context.runtime.getFalse();
        else if (data == null)
          return context.nil;
        ByteBuffer value = data.getValue();
        return context.runtime.newString(new ByteList(value.array(), value.arrayOffset() + value.position(), value.remaining()));
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    /******     PG::Connection INSTANCE METHODS: Control Functions     ******/

    @JRubyMethod
    public IRubyObject set_error_visibility(ThreadContext context, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject trace(ThreadContext context, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject untrace(ThreadContext context) {
        return context.nil;
    }

    /******     PG::Connection INSTANCE METHODS: Notice Processing     ******/

    @JRubyMethod
    public IRubyObject set_notice_receiver(ThreadContext context) {
        return context.nil;
    }

    @JRubyMethod
    public IRubyObject set_notice_processor(ThreadContext context) {
        return context.nil;
    }

    /******     PG::Connection INSTANCE METHODS: Other    ******/

    @JRubyMethod()
    public IRubyObject transaction(ThreadContext context, Block block) {
      if (!block.isGiven())
        throw context.runtime.newArgumentError("Must supply block for PG::Connection#transaction");

      try {
        try {
          postgresqlConnection.exec(BEGIN_QUERY);
          if (block.arity() == Arity.NO_ARGUMENTS)
            block.yieldSpecific(context);
          else
            block.yieldSpecific(context, this);
          postgresqlConnection.exec(COMMIT_QUERY);
        } catch (RuntimeException ex) {
          postgresqlConnection.exec(ROLLBACK_QUERY);
          throw ex;
        }
      } catch (Exception e) {
        throw context.runtime.newRuntimeError(e.getLocalizedMessage());
      }
      return context.nil;
    }

    @JRubyMethod(optional = 1)
    public IRubyObject block(ThreadContext context, IRubyObject[] args) {
      try {
        if (args.length == 0)
          postgresqlConnection.block();
        else {
          RubyFloat timeout = ((RubyNumeric) args[0]).convertToFloat();
          int timeoutMs = (int) (timeout.getDoubleValue() * 1000);
          postgresqlConnection.block(timeoutMs);
        }
        return context.nil;
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"wait_for_notify", "notifies_wait"}, optional = 1)
    public IRubyObject wait_for_notify(ThreadContext context, IRubyObject[] args, Block block) {
      try {
        NotificationResponse notification = postgresqlConnection.waitForNotify();
        if (block.isGiven()) {
          if (block.arity() == Arity.NO_ARGUMENTS) return block.call(context);
          RubyString condition = context.runtime.newString(notification.getCondition());
          RubyFixnum pid = context.runtime.newFixnum(notification.getPid());
          String javaPayload = notification.getPayload();
          IRubyObject payload = javaPayload == null ? context.nil : context.runtime.newString(javaPayload);
          if (!block.arity().isFixed()) {
            return block.call(context, condition, pid, payload);
          } else if (block.arity().required() == 2) {
            return block.call(context, condition, pid);
          } else if (block.arity().required() == 3) {
            return block.call(context, condition, pid, payload);
          }
          throw context.runtime.newArgumentError("Expected a block with arity 2 or 3");
        } else {
          return context.runtime.newString(notification.getCondition());
        }
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(required = 1)
    public IRubyObject quote_ident(ThreadContext context, IRubyObject identifier) {
      return quoteIdentifier(context, identifier);
    }

    @JRubyMethod
    public IRubyObject get_last_result(ThreadContext context) {
      try {
        ResultSet set = postgresqlConnection.getLastResult();
        return createResult(context, set, NULL_ARRAY, Block.NULL_BLOCK);
      } catch (Exception e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    /******     PG::Connection INSTANCE METHODS: Large Object Support     ******/

    @JRubyMethod(name = {"lo_creat", "locreat"}, optional = 1)
    public IRubyObject lo_creat(ThreadContext context, IRubyObject[] args) {
      try {
        LargeObjectAPI manager = postgresqlConnection.getLargeObjectAPI();
        int oid;
        if (args.length == 1)
          oid = manager.loCreat((Integer) args[0].toJava(Integer.class));
        else
          oid = manager.loCreat(0);
        return new RubyFixnum(context.runtime, oid);
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_create failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (IOException e) {
        throw newPgError(context, "lo_create failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_create", "locreate"})
    public IRubyObject lo_create(ThreadContext context, IRubyObject arg0) {
      try {
        LargeObjectAPI manager = postgresqlConnection.getLargeObjectAPI();
        int oid = manager.loCreate((Integer) arg0.toJava(Integer.class));
        return new RubyFixnum(context.runtime, oid);
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_create failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (IOException e) {
        throw newPgError(context, "lo_create failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_import", "loimport"})
    public IRubyObject lo_import(ThreadContext context, IRubyObject arg0) {
        return context.nil;
    }

    @JRubyMethod(name = {"lo_export", "loexport"})
    public IRubyObject lo_export(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        return context.nil;
    }

    @JRubyMethod(name = {"lo_open", "loopen"}, required = 1, optional = 1)
    public IRubyObject lo_open(ThreadContext context, IRubyObject [] args) {
      try {
        int fd;
        long oidLong = (Long) args[0].toJava(Long.class);
        if (args.length == 1)
          fd = postgresqlConnection.getLargeObjectAPI().loOpen((int) oidLong);
        else
          fd = postgresqlConnection.getLargeObjectAPI().loOpen((int) oidLong, (Integer) args[1].toJava(Integer.class));

        return context.runtime.newFixnum(fd);
      } catch (IOException e) {
        throw newPgError(context, "lo_open failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_open failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_write", "lowrite"}, required = 2, argTypes = {RubyFixnum.class, RubyString.class})
    public IRubyObject lo_write(ThreadContext context, IRubyObject object, IRubyObject buffer) {
      try {
        long fd = ((RubyFixnum) object).getLongValue();
        RubyString bufferString = (RubyString) buffer;
        int count = postgresqlConnection.getLargeObjectAPI().loWrite((int) fd, bufferString.getBytes());
        return context.runtime.newFixnum(count);
      } catch (IOException e) {
        throw newPgError(context, "lo_write failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_write failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_read", "loread"}, required = 2, argTypes = {RubyFixnum.class, RubyFixnum.class})
    public IRubyObject lo_read(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
      try {
        int fd = (int) ((RubyFixnum) arg0).getLongValue();
        int count = (int) ((RubyFixnum) arg1).getLongValue();
        byte[] b = postgresqlConnection.getLargeObjectAPI().loRead(fd, count);
        if (b.length == 0)
          return context.nil;
        return context.runtime.newString(new ByteList(b));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_read failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (IOException e) {
        throw newPgError(context, "lo_read failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_lseek", "lolseek", "lo_seek", "loseek"}, required = 3,
        argTypes = {RubyFixnum.class, RubyFixnum.class, RubyFixnum.class})
    public IRubyObject lo_lseek(ThreadContext context, IRubyObject _fd, IRubyObject _offset, IRubyObject _whence) {
      try {
        int offset = (int) ((RubyFixnum) _offset).getLongValue();
        int fd = (int) ((RubyFixnum) _fd).getLongValue();
        int whence = (int) ((RubyFixnum) _whence).getLongValue();
        int where = postgresqlConnection.getLargeObjectAPI().loSeek(fd, offset, whence);
        return new RubyFixnum(context.runtime, where);
      } catch (IOException e) {
        throw newPgError(context, "lo_lseek failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_lseek failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_tell", "lotell"}, required = 1, argTypes = {RubyFixnum.class})
    public IRubyObject lo_tell(ThreadContext context, IRubyObject _fd) {
      try {
        int fd = (int) ((RubyFixnum) _fd).getLongValue();
        int where = postgresqlConnection.getLargeObjectAPI().loTell(fd);
        return context.runtime.newFixnum(where);
      } catch (IOException e) {
        throw newPgError(context, "lo_tell failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_tell failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_truncate", "lotruncate"}, required = 2, argTypes = {RubyFixnum.class, RubyFixnum.class})
    public IRubyObject lo_truncate(ThreadContext context, IRubyObject _fd, IRubyObject _len) {
      try {
        int fd = (int) ((RubyFixnum) _fd).getLongValue();
        int len = (int) ((RubyFixnum) _len).getLongValue();
        int value = postgresqlConnection.getLargeObjectAPI().loTruncate(fd, len);
        return context.runtime.newFixnum(value);
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_tell failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      } catch (IOException e) {
        throw newPgError(context, "lo_tell failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_close", "loclose"}, required = 1, argTypes = {RubyFixnum.class})
    public IRubyObject lo_close(ThreadContext context, IRubyObject _fd) {
      try {
        int fd = (int) ((RubyFixnum) _fd).getLongValue();
        int value = postgresqlConnection.getLargeObjectAPI().loClose(fd);
        return context.runtime.newFixnum(value);
      } catch (IOException e) {
        throw newPgError(context, "lo_close failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_close failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod(name = {"lo_unlink", "lounlink"}, required = 1, argTypes = {RubyFixnum.class})
    public IRubyObject lo_unlink(ThreadContext context, IRubyObject _fd) {
      try {
        int fd = (int) ((RubyFixnum) _fd).getLongValue();
        int value = postgresqlConnection.getLargeObjectAPI().loUnlink(fd);
        return context.runtime.newFixnum(value);
      } catch (IOException e) {
        throw newPgError(context, "lo_unlink failed: " + e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, "lo_unlink failed: " + e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    /******     M17N     ******/

    @JRubyMethod
    public IRubyObject get_client_encoding(ThreadContext context) {
      return context.runtime.newString(postgresqlConnection.getClientEncoding());
    }

    @JRubyMethod(required = 1, alias = {"client_encoding="})
    public IRubyObject set_client_encoding(ThreadContext context, IRubyObject encoding) {
      return setClientEncodingCommon(context, encoding.asJavaString());
    }

    @JRubyMethod
    public IRubyObject internal_encoding(ThreadContext context) {
      String encoding = postgresqlConnection.getClientEncoding();
      return findEncoding(context, postgresEncodingToRubyEncoding.get(encoding));
    }

    @JRubyMethod(name = "internal_encoding=")
    public IRubyObject set_internal_encoding(ThreadContext context, IRubyObject encoding) {
      try {
        String postgresEncoding;
        if (encoding instanceof RubyString) {
          postgresEncoding = encoding.asJavaString();
        } else if (encoding instanceof RubyEncoding) {
          postgresEncoding = ((RubyEncoding) encoding).to_s(context).asJavaString();
        } else {
          postgresEncoding = "SQL_ASCII";
        }
        if (rubyEncodingToPostgresEncoding.containsKey(postgresEncoding))
            postgresEncoding = rubyEncodingToPostgresEncoding.get(postgresEncoding);
        else
          postgresEncoding = "SQL_ASCII";
        postgresqlConnection.setClientEncoding(postgresEncoding);
        return context.nil;
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), getClientEncodingAsJavaEncoding(context));
      }
    }

    @JRubyMethod
    public IRubyObject external_encoding(ThreadContext context) {
      String encoding = postgresqlConnection.getServerEncoding();
      return findEncoding(context, postgresEncodingToRubyEncoding.get(encoding));
    }

    @JRubyMethod
    public IRubyObject set_default_encoding(ThreadContext context) {
      IRubyObject _internal_encoding = RubyEncoding.getDefaultInternal(this);
      if (_internal_encoding.isNil())
        return context.nil;
      return set_internal_encoding(context, _internal_encoding);
    }

    private static String[] tokenizeString(String asJavaString) {
      List<String> tokens = new ArrayList<String>();
      StringBuffer currentToken = new StringBuffer();
      boolean insideSingleQuote = false;
      boolean escapeNextCharacter = false;
      for (int i = 0; i < asJavaString.length(); i++) {
        char currentChar = asJavaString.charAt(i);

        // finish the current token and append it if:
        //   - we just hit an equal sign or a space and we're not inside single quotes
        //   - we just hit a single quote and we're inside a token
        //   - we're at the end of the input string
        if ((currentChar == '=' || Character.isWhitespace(currentChar)) && !insideSingleQuote ||
            (currentChar == '\'' && !escapeNextCharacter && insideSingleQuote)) {
          if (insideSingleQuote)
            insideSingleQuote = false;
          tokens.add(currentToken.toString());
          currentToken = new StringBuffer();
          // get rid of all the whitespaces and equal sign that follow
          i = consumeWhiteSpaceAndEqual(asJavaString, i);
        } else if (currentChar == '\\' && !escapeNextCharacter) {
          escapeNextCharacter = true;
        } else if (currentChar == '\'' && !escapeNextCharacter) {
          // don't add the last single quote. we just started a new token
          // surrounded by single quotes
          insideSingleQuote = true;
        } else {
          escapeNextCharacter = false;
          currentToken.append(currentChar);
        }
      }
      if (currentToken.length() != 0)
        tokens.add(currentToken.toString());
      return tokens.toArray(new String[tokens.size()]);
    }

    private static int consumeWhiteSpaceAndEqual(String string, int currentIndex) {
      for (int i = currentIndex + 1; i < string.length(); i++) {
        char currentCharacter = string.charAt(i);
        if (!Character.isWhitespace(currentCharacter) && currentCharacter != '=')
          return i - 1;
      }
      return string.length();
    }

    private PostgresqlString rubyStringAsPostgresqlString(IRubyObject str) {
      return new PostgresqlString(((RubyString) str).getBytes());
    }

    private IRubyObject setClientEncodingCommon(ThreadContext context, String encoding) {
      try {
        postgresqlConnection.setClientEncoding(encoding);
        return context.nil;
      } catch (IOException e) {
        throw newPgError(context, e.getLocalizedMessage(), null, this.getClientEncodingAsJavaEncoding(context));
      } catch (PostgresqlException e) {
        throw newPgError(context, e.getLocalizedMessage(), e.getResultSet(), this.getClientEncodingAsJavaEncoding(context));
      }
    }

    private Encoding getClientEncodingAsJavaEncoding(ThreadContext context) {
      IRubyObject encoding = internal_encoding(context);
      if (encoding.isNil())
        return null;
      return ((RubyEncoding) encoding).getEncoding();
    }

    private IRubyObject findEncoding(ThreadContext context, String encodingName) {
      IRubyObject rubyEncodingName = encodingName == null ? context.nil : context.runtime.newString(encodingName);
      return findEncoding(context, rubyEncodingName);
    }

    private IRubyObject findEncoding(ThreadContext context, IRubyObject encodingName) {
      IRubyObject encoding = context.nil;
      try {
        if (!encodingName.isNil())
          encoding = context.runtime.getClass("Encoding").callMethod("find", encodingName);
      } catch (RuntimeException e) {
      }
      if (encoding.isNil())
        encoding = context.runtime.getClass("Encoding").getConstant("ASCII_8BIT");
      return encoding;
    }
}
TOP

Related Classes of org.jruby.pg.Connection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.