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
@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
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
@visibility private
# File lib/ensql/postgres_adapter.rb, line 96 def fetch_count(sql) execute(sql, &:cmd_tuples) end
@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
@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
@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
@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
@visibility private
# File lib/ensql/postgres_adapter.rb, line 124 def fetch_rows(sql) fetch_result(sql, &:to_a) end
@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
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
@visibility private
# File lib/ensql/postgres_adapter.rb, line 80 def run(sql) execute(sql) { nil } end
Private Instance Methods
# 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
# File lib/ensql/postgres_adapter.rb, line 137 def execute(sql, &block) @pool.with { |c| c.async_exec(sql, &block) } end
# 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
# File lib/ensql/postgres_adapter.rb, line 153 def result_type_map @result_type_map ||= @pool.with { |c| PG::BasicTypeMapForResults.new(c) } end
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