Class: PGx::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/pgx.rb

Overview

Extends the PG::Connection methods by adding logging and retrying capabilities (synchronous methods only.) The arguments and method behaviors are identical, except for the ‘new’ method.

See Also:

Author:

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Connection

Returns a new instance of Connection.

Examples:

log = Logger.new('/tmp/debug.log');
log.level = Logger::DEBUG
connect_sleeper = lambda { |try_count|
    sleep [try_count ** 2, 120].min
}
con = PGx::Connection.new(
    logger: log,
    connect_retry_sleep: connect_sleeper,
    dbname: 'test'
)

Parameters:

  • logger: (Logger)

    a logger object, or a lambda returning the logger object. Note that a lambda returning the logger is preferable because it makes logging of the log object itself easier on the eyes. This parameter can be a single logger object, or an array of logger objects in case you want to log to multiple logs (eg: stdout and a log file.)

  • connect_init: (Proc)

    a Proc that contains code that will always be executed every time a new connection is established. It is also executed after a reset(). May be useful for things such as setting the search_path.

  • connect_retry_sleep: (Proc, Integer)

    a Proc or Integer. If a Proc is passed, the Proc will be called with one argument, try_count, which starts at 1 and increments by one with every unccesseful connection attempt. The Proc should implement the algorithm for sleeping between connection attempts. If an Integer is passed, it indicates how long to sleep between connection attempts, in seconds. If nil is passed, no attempts to reconnect will be made, it simply raises an exception.

  • method_try_count_max: (Integer)

    the maximum number of times a single method, or transaction block, can be tried

  • method_retriable_error_states: (Array<String>)

    the set of error states for which a single method, or transaction block, should be retried

  • ...

    The rest of the arguments are the same as the base class method arguments

See Also:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/pgx.rb', line 86

def initialize(*args)
    connect_args = PGx.const_get('CONNECT_ARGS')
    (our_args, pg_args) = extract_our_args(connect_args, args)

    log = arg_with_default(connect_args, our_args, :logger)
    @logger = [ log.is_a?(Proc) ? log.call : log ].flatten
    @connect_init = arg_with_default(connect_args, our_args, :connect_init)
    @connect_retry_sleep = arg_with_default(connect_args, our_args, :connect_retry_sleep)
    @method_try_count_max = arg_with_default(connect_args, our_args, :method_try_count_max)
    @method_retriable_error_states = arg_with_default(connect_args, our_args, :method_retriable_error_states)

    # gets set in connect_log_and_try() to the connection handle
    # needed by reset() to be able to execute the connect_init proc
    @connection = nil

    # always include invalid_sql_statement_name (required for prepared statements to work after re-connects)
    @method_retriable_error_states.push('26000') unless @method_retriable_error_states.include?('26000')

    @in_transaction_block = false

    @prepared_statements = {}  # hash of all statements ever prepared, key = statement name, value = sql
    @prepared_live = {} # keeps track which prepared statements are currently prepared
    @transaction_reprepare = nil # placeholder for statement to be prepared before transaction retry

    @connect_proc = lambda {
        @prepared_live = {} # reset on connect
        connect_log_and_try(
            lambda { PG::Connection.new(*pg_args) },
            our_args,
            pg_args
        )
    }
    connect_loop()
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(m, *args, &block) ⇒ Object



121
122
123
# File 'lib/pgx.rb', line 121

def method_missing(m, *args, &block)
    sql_log_and_try(lambda { @connection.send(m, *args, &block) }, m, args)
end

Instance Method Details

#prepare(*args, &block) ⇒ Object

Examples:

con.prepare 'sql1', 'select * from mytable limit $1'
con.exec_prepared 'sql1', [100] do |rs|
    puts rs.fields.collect { |fname| "%-15s" % [fname] }.join('')
    rs.values.collect { |row|
        puts row.collect { |col| "%-15s" % [col] }.join('')
    }
end


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/pgx.rb', line 146

def prepare(*args, &block)
    # it's possible to call prepare() on an statement that is already prepared
    # if you have a prepare() inside a transaction, and the transaction is retried
    # thus we check if it's already live
    s = args[0]
    r = nil
    if @prepared_live.has_key?(s)
        log_debug %Q{prepared statement #{s} is already live, skipping re-preparing it}
    else
        r = sql_log_and_try(lambda { @connection.prepare(*args, &block) }, __method__, args)
        @prepared_statements[s] = args[1]
        @prepared_live[s] = true
    end
    r
end

#reset(*args, &block) ⇒ Object



162
163
164
165
166
167
# File 'lib/pgx.rb', line 162

def reset(*args, &block)
    r = sql_log_and_try(lambda { @connection.reset(*args, &block) }, __method__, args)
    @prepared_live = {} # reset on connect
    @connect_init.call(@connection)
    r
end

#transaction(*args, &block) ⇒ Object

Examples:

con.transaction do
    con.exec "create temp table mytable(x text)"
    con.exec "insert into mytable(x) values('a')"
    con.exec "insert into mytable(x) values('b')"
end


131
132
133
134
135
136
# File 'lib/pgx.rb', line 131

def transaction(*args, &block)
    @in_transaction_block = true
    r = sql_log_and_try(lambda { @connection.transaction(*args, &block) }, __method__, args)
    @in_transaction_block = false
    r
end