class Ensql::PostgresAdapter

Wraps a pool of PG connections to implement the {Adapter} interface. The adapter can use a 3rd-party pool (e.g. from ActiveRecord of Sequel) or manage its own using the simple [connection_pool gem](github.com/mperham/connection_pool).

This adapter is much faster and offers much better PostgreSQL specific parameter interpolation than the framework adapters. See {query_type_map} for options.

@example

# Use with ActiveRecord's connection pool
Ensql.adapter = Ensql::PostgresAdapter.new(Ensql::ActiveRecordAdapter.pool)

# Use with Sequel's connection pool
DB = Sequel.connect(ENV['DATABASE_URL'])
Ensql.adapter = Ensql::PostgresAdapter.new(Ensql::SequelAdapter.pool(DB))

# Use with our own thread-safe connection pool
Ensql.adapter = Ensql::PostgresAdapter.pool { PG.connect ENV['DATABASE_URL'] }
Ensql.adapter = Ensql::PostgresAdapter.pool(size: 5) { PG.connect ENV['DATABASE_URL'] }

@see SUPPORTED_PG_VERSIONS

Public Class Methods

new(pool) click to toggle source

@param pool [PoolWrapper, ConnectionPool, with] a object that yields a PG::Connection using `#with`

# File lib/ensql/postgres_adapter.rb, line 50
def initialize(pool)
  @pool = pool
  @quoter = PG::TextEncoder::QuotedLiteral.new
end
pool(**pool_opts, &connection_block) click to toggle source

Set up a connection pool using the supplied block to initialise connections.

PostgresAdapter.pool(size: 20) { PG.connect ENV['DATABASE_URL'] }

@param pool_opts are sent straight to the ConnectionPool initializer. @option pool_opts [Integer] timeout (5) number of seconds to wait for a connection if none currently available. @option pool_opts [Integer] size (5) number of connections to pool. @yieldreturn [PG::Connection] a new connection.

# File lib/ensql/postgres_adapter.rb, line 45
def self.pool(**pool_opts, &connection_block)
  new ConnectionPool.new(**pool_opts, &connection_block)
end

Public Instance Methods

fetch_count(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 96
def fetch_count(sql)
  execute(sql, &:cmd_tuples)
end
fetch_each_row(sql, &block) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 117
def fetch_each_row(sql, &block)
  return to_enum(:fetch_each_row, sql) unless block

  fetch_result(sql) { |res| res.each(&block) }
end
fetch_first_column(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 111
def fetch_first_column(sql)
  # Return an array of nils if we don't have a column
  fetch_result(sql) { |res| res.nfields > 0 ? res.column_values(0) : Array.new(res.ntuples) }
end
fetch_first_field(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 101
def fetch_first_field(sql)
  fetch_result(sql) { |res| res.getvalue(0, 0) if res.ntuples > 0 && res.nfields > 0 }
end
fetch_first_row(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 106
def fetch_first_row(sql)
  fetch_result(sql) { |res| res[0] if res.ntuples > 0 }
end
fetch_rows(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 124
def fetch_rows(sql)
  fetch_result(sql, &:to_a)
end
literalize(value) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 85
def literalize(value)
  case value
  when NilClass then "NULL"
  when Numeric, TrueClass, FalseClass then value.to_s
  when String then @quoter.encode(value)
  else
    @quoter.encode(serialize(value))
  end
end
query_type_map() click to toggle source

A map for encoding Ruby objects into PostgreSQL literals based on their class. You can add additional class mappings to suit your needs. See rubydoc.info/gems/pg/PG/BasicTypeRegistry for details of adding your own encoders and decoders.

# Encode any `IPAddr` objects for interpolation using the `InetEncoder`.
Ensql.adapter.query_type_map[IPAddr] = InetEncoder.new

# Deserialize `inet` columns as IPAddr objects.
PG::BasicTypeRegistry.register_type(0, 'inet', InetEncoder, InetDecoder)

@return [PG::TypeMapByClass]

# File lib/ensql/postgres_adapter.rb, line 67
def query_type_map
  @query_type_map ||= @pool.with do |connection|
    map = PG::BasicTypeMapForQueries.new(connection)
    # Ensure encoders are set up for old versions of the pg gem
    map[Date] ||= PG::TextEncoder::Date.new
    map[Time] ||= PG::TextEncoder::TimestampWithoutTimeZone.new
    map[Hash] ||= PG::TextEncoder::JSON.new
    map[BigDecimal] ||= NumericEncoder.new
    map
  end
end
run(sql) click to toggle source

@visibility private

# File lib/ensql/postgres_adapter.rb, line 80
def run(sql)
  execute(sql) { nil }
end

Private Instance Methods

encoder_for(value) click to toggle source
# File lib/ensql/postgres_adapter.rb, line 147
def encoder_for(value)
  coder = query_type_map[value.class]
  # Handle the weird case where coder can be a method name
  coder.is_a?(Symbol) ? query_type_map.send(coder, value) : coder
end
execute(sql, &block) click to toggle source
# File lib/ensql/postgres_adapter.rb, line 137
def execute(sql, &block)
  @pool.with { |c| c.async_exec(sql, &block) }
end
fetch_result(sql) { |res| ... } click to toggle source
# File lib/ensql/postgres_adapter.rb, line 130
def fetch_result(sql)
  execute(sql) do |res|
    res.type_map = result_type_map
    yield res
  end
end
result_type_map() click to toggle source
# File lib/ensql/postgres_adapter.rb, line 153
def result_type_map
  @result_type_map ||= @pool.with { |c| PG::BasicTypeMapForResults.new(c) }
end
serialize(value) click to toggle source

Use PG's built-in type mapping to serialize objects into SQL strings.

# File lib/ensql/postgres_adapter.rb, line 142
def serialize(value)
  (coder = encoder_for(value)) || raise(TypeError, "No SQL serializer for #{value.class}")
  coder.encode(value)
end