Skip to content

Commit

Permalink
Permit building coder maps ahead-of-time of TypeMap init
Browse files Browse the repository at this point in the history
Building coder maps can be quite expensive, building them ahead-of-time
allows them to be reused for multiple type map creations, e.g. if
creating both BasicTypeMapForQueries and BasicTypeMapForResults the cost
need only be paid once.

Example usage:

    conn = PG.connect(…)
    coder_maps = BasicTypeRegistry.build_coder_maps(conn)
    tmq = BasicTypeMapForQueries.new(conn, coder_maps: coder_maps)
    tmr = BasicTypeMapForResults.new(conn, coder_maps: coder_maps)

Testing methodology:

- 189k rows returned from pg_type query in `build_coder_maps`.
- Cached and injected results from query to remove query cost and
  jitter from baseline.
- Timing calling `PG::BasicTypeMapForQueries.new` only, figures are
  average of 32 iterations.
- “Cached” calls `build_coder_maps` ahead-of-time, outside the timing
  loop, and passes-in.

          runtime
Baseline:  374±16 ms
Cached  :   <1± 0 ms (-99%)

This confirms that the majority of the cost in creating a new type map
is, when ignoring SQL query and results materialization, enclosed within
`build_coder_maps`. As such, for *n* type maps, this reduces the
(sometimes non-small) time complexity effectively from O(n) → O(1).
  • Loading branch information
amarshall committed Apr 8, 2021
1 parent 4f38d28 commit 4ac7211
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 8 deletions.
13 changes: 5 additions & 8 deletions lib/pg/basic_type_mapping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ def coder_by_oid(oid)
end
end

private

def build_coder_maps(connection)
result = connection.exec(<<-SQL).to_a
SELECT t.oid, t.typname, t.typelem, t.typdelim, ti.proname AS typinput
Expand All @@ -107,6 +105,7 @@ def build_coder_maps(connection)
h
end
end
module_function :build_coder_maps

ValidFormats = { 0 => true, 1 => true }
ValidDirections = { :encoder => true, :decoder => true }
Expand All @@ -122,8 +121,6 @@ def check_format_and_direction(format, direction)
# objects as values.
CODERS_BY_NAME = []

public

# Register an encoder or decoder instance for casting a PostgreSQL type.
#
# Coder#name must correspond to the +typname+ column in the +pg_type+ table.
Expand Down Expand Up @@ -290,8 +287,8 @@ def typecast_result_value(result, _tuple, field)
end
end

def initialize(connection)
@coder_maps = build_coder_maps(connection)
def initialize(connection, coder_maps: nil)
@coder_maps = coder_maps || build_coder_maps(connection)

# Populate TypeMapByOid hash with decoders
@coder_maps.flat_map{|f| f[:decoder].coders }.each do |coder|
Expand Down Expand Up @@ -382,8 +379,8 @@ class BinaryData < String

include PG::BasicTypeRegistry

def initialize(connection)
@coder_maps = build_coder_maps(connection)
def initialize(connection, coder_maps: nil)
@coder_maps = coder_maps || build_coder_maps(connection)
@array_encoders_by_klass = array_encoders_by_klass
@encode_array_as = :array
init_encoders
Expand Down
8 changes: 8 additions & 0 deletions spec/pg/basic_type_mapping_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,4 +635,12 @@ def to_s
end
end
end

describe PG::BasicTypeRegistry do
it "can build coder maps" do
expect do
described_class.build_coder_maps(@conn)
end.to_not raise_error
end
end
end

0 comments on commit 4ac7211

Please sign in to comment.