em_postgresql 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +43 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/em_postgresql_adapter.rb +116 -0
- data/lib/active_record/patches.rb +102 -0
- data/lib/fiber_pool.rb +82 -0
- data/lib/postgres_connection.rb +209 -0
- data/test/database.yml +10 -0
- data/test/test_database.rb +51 -0
- metadata +97 -0
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
em_postgresql
|
2
|
+
---------------
|
3
|
+
|
4
|
+
An EventMachine-aware driver for using Postgresql with ActiveRecord.
|
5
|
+
|
6
|
+
Requirements
|
7
|
+
==============
|
8
|
+
|
9
|
+
* Ruby 1.9
|
10
|
+
* EventMachine 0.12.10
|
11
|
+
* postgres-pr 0.6.1
|
12
|
+
* Rails 2.3.5
|
13
|
+
|
14
|
+
Tested with these version, other versions might work. YMMV.
|
15
|
+
|
16
|
+
You CANNOT have the **pg** gem installed. ActiveRecord prefers the **pg** gem but this code requires
|
17
|
+
the **postgres-pr** gem to be loaded. I'm not sure if there is a way to make them live together in harmony.
|
18
|
+
|
19
|
+
You'll need to ensure your code is running within an active Fiber using the FiberPool defined in fiber_pool.rb. If you are running Rails in Thin, the following code is a good place to start to figure out how to do this:
|
20
|
+
|
21
|
+
<http://github.com/espace/neverblock/blob/master/lib/never_block/servers/thin.rb>
|
22
|
+
|
23
|
+
Usage
|
24
|
+
=======
|
25
|
+
|
26
|
+
List this gem in your `config/environment.rb`:
|
27
|
+
|
28
|
+
config.gem 'postgres-pr', :lib => false
|
29
|
+
config.gem 'em_postgresql', :lib => false
|
30
|
+
|
31
|
+
and update your `config/database.yml` to contain the proper adapter attribute:
|
32
|
+
|
33
|
+
adapter: em_postgresql
|
34
|
+
|
35
|
+
|
36
|
+
Author
|
37
|
+
=========
|
38
|
+
|
39
|
+
Mike Perham, mperham AT gmail.com,
|
40
|
+
[Github](http://github.com/mperham),
|
41
|
+
[Twitter](http://twitter.com/mperham),
|
42
|
+
[Blog](http://mikeperham.com)
|
43
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# vim: syntax=Ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = "em_postgresql"
|
9
|
+
s.summary = s.description = "An ActiveRecord driver for using Postgresql with EventMachine"
|
10
|
+
s.email = "mperham@gmail.com"
|
11
|
+
s.homepage = "http://github.com/mperham/em_postgresql"
|
12
|
+
s.authors = ['Mike Perham']
|
13
|
+
s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
14
|
+
s.test_files = FileList["test/test_*.rb"]
|
15
|
+
s.add_dependency 'postgres-pr', '>=0.6.1'
|
16
|
+
s.add_dependency 'eventmachine', '>=0.12.10'
|
17
|
+
end
|
18
|
+
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
task :gin => [:gemspec, :build] do
|
25
|
+
puts `gem install pkg/em_postgresql-#{File.read('VERSION').strip}.gem`
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
Rake::TestTask.new
|
30
|
+
|
31
|
+
task :default => :test
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'postgres_connection'
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
5
|
+
require 'active_record/patches'
|
6
|
+
|
7
|
+
if !PGconn.respond_to?(:quote_ident)
|
8
|
+
def PGconn.quote_ident(name)
|
9
|
+
%("#{name}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActiveRecord
|
14
|
+
module ConnectionAdapters
|
15
|
+
|
16
|
+
class EmPostgreSQLAdapter < PostgreSQLAdapter
|
17
|
+
# checkin :logi
|
18
|
+
# checkout :logo
|
19
|
+
#
|
20
|
+
# def logo
|
21
|
+
# puts "#{Fiber.current.object_id} #{self.object_id} checkout"
|
22
|
+
# end
|
23
|
+
# def logi
|
24
|
+
# puts "#{Fiber.current.object_id} #{self.object_id} checkin"
|
25
|
+
# end
|
26
|
+
|
27
|
+
def initialize(connection, logger, host_parameters, connection_parameters, config)
|
28
|
+
@hostname = host_parameters[0]
|
29
|
+
@port = host_parameters[1]
|
30
|
+
@connect_parameters, @config = connection_parameters, config
|
31
|
+
super(connection, logger, nil, config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect
|
35
|
+
@logger.info "Connecting to #{@hostname}:#{@port}"
|
36
|
+
@connection = ::EM.connect(@hostname, @port, ::EM::P::PostgresConnection)
|
37
|
+
|
38
|
+
fiber = Fiber.current
|
39
|
+
yielding = true
|
40
|
+
result = false
|
41
|
+
message = nil
|
42
|
+
task = @connection.connect(*@connect_parameters)
|
43
|
+
task.callback do |rc, msg|
|
44
|
+
result = rc
|
45
|
+
message = msg
|
46
|
+
fiber.resume
|
47
|
+
end
|
48
|
+
task.errback do |msg|
|
49
|
+
result = false
|
50
|
+
message = msg
|
51
|
+
yielding = false
|
52
|
+
end
|
53
|
+
Fiber.yield if yielding
|
54
|
+
|
55
|
+
raise RuntimeError, "Connection failed: #{message}" if !result
|
56
|
+
|
57
|
+
# Use escape string syntax if available. We cannot do this lazily when encountering
|
58
|
+
# the first string, because that could then break any transactions in progress.
|
59
|
+
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
60
|
+
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
61
|
+
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
62
|
+
if supports_standard_conforming_strings?
|
63
|
+
self.class.instance_eval do
|
64
|
+
define_method(:quoted_string_prefix) { 'E' }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
69
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
70
|
+
# should know about this but can't detect it there, so deal with it here.
|
71
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
72
|
+
PostgreSQLColumn.module_eval(<<-end_eval)
|
73
|
+
def extract_precision(sql_type) # def extract_precision(sql_type)
|
74
|
+
if sql_type =~ /^money$/ # if sql_type =~ /^money$/
|
75
|
+
#{money_precision} # 19
|
76
|
+
else # else
|
77
|
+
super # super
|
78
|
+
end # end
|
79
|
+
end # end
|
80
|
+
end_eval
|
81
|
+
|
82
|
+
configure_connection
|
83
|
+
@connection
|
84
|
+
end
|
85
|
+
|
86
|
+
def active?
|
87
|
+
!@connection.closed? && @connection.exec('SELECT 1')
|
88
|
+
rescue RuntimeError => re
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Base
|
96
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
97
|
+
def self.em_postgresql_connection(config) # :nodoc:
|
98
|
+
config = config.symbolize_keys
|
99
|
+
host = config[:host]
|
100
|
+
port = config[:port] || 5432
|
101
|
+
username = config[:username].to_s if config[:username]
|
102
|
+
password = config[:password].to_s if config[:password]
|
103
|
+
|
104
|
+
if config.has_key?(:database)
|
105
|
+
database = config[:database]
|
106
|
+
else
|
107
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
108
|
+
end
|
109
|
+
|
110
|
+
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
111
|
+
# so just pass a nil connection object for the time being.
|
112
|
+
ConnectionAdapters::EmPostgreSQLAdapter.new(nil, logger, [host, port], [database, username, password], config)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
|
4
|
+
def self.fiber_pools
|
5
|
+
@fiber_pools ||= []
|
6
|
+
end
|
7
|
+
def self.register_fiber_pool(fp)
|
8
|
+
fiber_pools << fp
|
9
|
+
end
|
10
|
+
|
11
|
+
class FiberedMonitor
|
12
|
+
class Queue
|
13
|
+
def initialize
|
14
|
+
@queue = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait(timeout)
|
18
|
+
t = timeout || 5
|
19
|
+
fiber = Fiber.current
|
20
|
+
x = EM::Timer.new(t) do
|
21
|
+
@queue.delete(fiber)
|
22
|
+
fiber.resume(false)
|
23
|
+
end
|
24
|
+
@queue << fiber
|
25
|
+
returning Fiber.yield do
|
26
|
+
x.cancel
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def signal
|
31
|
+
fiber = @queue.pop
|
32
|
+
fiber.resume(true) if fiber
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def synchronize
|
37
|
+
yield
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_cond
|
41
|
+
Queue.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# ActiveRecord's connection pool is based on threads. Since we are working
|
46
|
+
# with EM and a single thread, multiple fiber design, we need to provide
|
47
|
+
# our own connection pool that keys off of Fiber.current so that different
|
48
|
+
# fibers running in the same thread don't try to use the same connection.
|
49
|
+
class ConnectionPool
|
50
|
+
def initialize(spec)
|
51
|
+
@spec = spec
|
52
|
+
|
53
|
+
# The cache of reserved connections mapped to threads
|
54
|
+
@reserved_connections = {}
|
55
|
+
|
56
|
+
# The mutex used to synchronize pool access
|
57
|
+
@connection_mutex = FiberedMonitor.new
|
58
|
+
@queue = @connection_mutex.new_cond
|
59
|
+
|
60
|
+
# default 5 second timeout unless on ruby 1.9
|
61
|
+
@timeout = spec.config[:wait_timeout] || 5
|
62
|
+
|
63
|
+
# default max pool size to 5
|
64
|
+
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
65
|
+
|
66
|
+
@connections = []
|
67
|
+
@checked_out = []
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def current_connection_id #:nodoc:
|
73
|
+
Fiber.current.object_id
|
74
|
+
end
|
75
|
+
|
76
|
+
# Remove stale fibers from the cache.
|
77
|
+
def remove_stale_cached_threads!(cache, &block)
|
78
|
+
keys = Set.new(cache.keys)
|
79
|
+
|
80
|
+
ActiveRecord::ConnectionAdapters.fiber_pools.each do |pool|
|
81
|
+
pool.busy_fibers.each_pair do |object_id, fiber|
|
82
|
+
keys.delete(object_id)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# puts "Pruning stale connections: #{f.busy_fibers.size} #{f.fibers.size} #{keys.inspect}"
|
86
|
+
keys.each do |key|
|
87
|
+
next unless cache.has_key?(key)
|
88
|
+
block.call(key, cache[key])
|
89
|
+
cache.delete(key)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def checkout_and_verify(c)
|
94
|
+
@checked_out << c
|
95
|
+
c.run_callbacks :checkout
|
96
|
+
c.verify!
|
97
|
+
c
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
data/lib/fiber_pool.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Author:: Mohammad A. Ali (mailto:oldmoe@gmail.com)
|
2
|
+
# Copyright:: Copyright (c) 2008 eSpace, Inc.
|
3
|
+
# License:: Distributes under the same terms as Ruby
|
4
|
+
|
5
|
+
require 'fiber'
|
6
|
+
|
7
|
+
class Fiber
|
8
|
+
|
9
|
+
#Attribute Reference--Returns the value of a fiber-local variable, using
|
10
|
+
#either a symbol or a string name. If the specified variable does not exist,
|
11
|
+
#returns nil.
|
12
|
+
def [](key)
|
13
|
+
local_fiber_variables[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
#Attribute Assignment--Sets or creates the value of a fiber-local variable,
|
17
|
+
#using either a symbol or a string. See also Fiber#[].
|
18
|
+
def []=(key,value)
|
19
|
+
local_fiber_variables[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def local_fiber_variables
|
25
|
+
@local_fiber_variables ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FiberPool
|
30
|
+
|
31
|
+
# gives access to the currently free fibers
|
32
|
+
attr_reader :fibers
|
33
|
+
attr_reader :busy_fibers
|
34
|
+
|
35
|
+
# Code can register a proc with this FiberPool to be called
|
36
|
+
# every time a Fiber is finished. Good for releasing resources
|
37
|
+
# like ActiveRecord database connections.
|
38
|
+
attr_accessor :generic_callbacks
|
39
|
+
|
40
|
+
# Prepare a list of fibers that are able to run different blocks of code
|
41
|
+
# every time. Once a fiber is done with its block, it attempts to fetch
|
42
|
+
# another one from the queue
|
43
|
+
def initialize(count = 100)
|
44
|
+
@fibers,@busy_fibers,@queue,@generic_callbacks = [],{},[],[]
|
45
|
+
count.times do |i|
|
46
|
+
fiber = Fiber.new do |block|
|
47
|
+
loop do
|
48
|
+
block.call
|
49
|
+
# callbacks are called in a reverse order, much like c++ destructor
|
50
|
+
Fiber.current[:callbacks].pop.call while Fiber.current[:callbacks].length > 0
|
51
|
+
generic_callbacks.each do |cb|
|
52
|
+
cb.call
|
53
|
+
end
|
54
|
+
unless @queue.empty?
|
55
|
+
block = @queue.shift
|
56
|
+
else
|
57
|
+
@busy_fibers.delete(Fiber.current.object_id)
|
58
|
+
@fibers << Fiber.current
|
59
|
+
block = Fiber.yield
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
fiber[:callbacks] = []
|
64
|
+
fiber[:em_keys] = []
|
65
|
+
@fibers << fiber
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# If there is an available fiber use it, otherwise, leave it to linger
|
70
|
+
# in a queue
|
71
|
+
def spawn(&block)
|
72
|
+
if fiber = @fibers.shift
|
73
|
+
fiber[:callbacks] = []
|
74
|
+
@busy_fibers[fiber.object_id] = fiber
|
75
|
+
fiber.resume(block)
|
76
|
+
else
|
77
|
+
@queue << block
|
78
|
+
end
|
79
|
+
self # we are keen on hiding our queue
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'postgres-pr/message'
|
3
|
+
require 'postgres-pr/connection'
|
4
|
+
require 'stringio'
|
5
|
+
require 'fiber'
|
6
|
+
|
7
|
+
class StringIO # :nodoc:
|
8
|
+
# Reads exactly +n+ bytes.
|
9
|
+
#
|
10
|
+
# If the data read is nil an EOFError is raised.
|
11
|
+
#
|
12
|
+
# If the data read is too short a TruncatedDataError is raised and the read
|
13
|
+
# data is obtainable via its #data method.
|
14
|
+
def readbytes(n)
|
15
|
+
str = read(n)
|
16
|
+
if str == nil
|
17
|
+
raise EOFError, "End of file reached"
|
18
|
+
end
|
19
|
+
if str.size < n
|
20
|
+
raise TruncatedDataError.new("data truncated", str)
|
21
|
+
end
|
22
|
+
str
|
23
|
+
end
|
24
|
+
alias read_exactly_n_bytes readbytes
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module EventMachine
|
29
|
+
module Protocols
|
30
|
+
class PostgresConnection < EventMachine::Connection
|
31
|
+
include PostgresPR
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@data = ""
|
35
|
+
@params = {}
|
36
|
+
@connected = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fibered impl for synchronous execution of SQL within EM
|
40
|
+
def exec(sql)
|
41
|
+
fiber = Fiber.current
|
42
|
+
# p [fiber.object_id, self.object_id, sql]
|
43
|
+
yielding = true
|
44
|
+
(status, result, errors) = nil
|
45
|
+
d = query(sql)
|
46
|
+
d.callback do |s, r, e|
|
47
|
+
(status, result, errors) = s, r, e
|
48
|
+
fiber.resume
|
49
|
+
end
|
50
|
+
d.errback do |msg|
|
51
|
+
errors = msg
|
52
|
+
status = false
|
53
|
+
# errback is called from the same fiber
|
54
|
+
yielding = false
|
55
|
+
end
|
56
|
+
|
57
|
+
Fiber.yield if yielding
|
58
|
+
# p [fiber.object_id, self.object_id, result]
|
59
|
+
return PGresult.new(result) if status
|
60
|
+
raise RuntimeError, (errors || result).inspect
|
61
|
+
end
|
62
|
+
|
63
|
+
def close
|
64
|
+
close_connection
|
65
|
+
end
|
66
|
+
|
67
|
+
def closed?
|
68
|
+
!@connected
|
69
|
+
end
|
70
|
+
|
71
|
+
def post_init
|
72
|
+
@connected = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def unbind
|
76
|
+
@connected = false
|
77
|
+
if o = (@pending_query || @pending_conn)
|
78
|
+
o.succeed false, "lost connection"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def connect(db, user, psw=nil)
|
83
|
+
d = EM::DefaultDeferrable.new
|
84
|
+
d.timeout 15
|
85
|
+
|
86
|
+
if @pending_query || @pending_conn
|
87
|
+
d.fail "Operation already in progress"
|
88
|
+
else
|
89
|
+
@pending_conn = d
|
90
|
+
prms = {"user"=>user, "database"=>db}
|
91
|
+
@user = user
|
92
|
+
if psw
|
93
|
+
@password = psw
|
94
|
+
#prms["password"] = psw
|
95
|
+
end
|
96
|
+
send_data PostgresPR::StartupMessage.new( 3 << 16, prms ).dump
|
97
|
+
end
|
98
|
+
|
99
|
+
d
|
100
|
+
end
|
101
|
+
|
102
|
+
def query(sql)
|
103
|
+
d = EM::DefaultDeferrable.new
|
104
|
+
d.timeout 15
|
105
|
+
|
106
|
+
if !@connected
|
107
|
+
d.fail "Not connected"
|
108
|
+
elsif @pending_query || @pending_conn
|
109
|
+
d.fail "Operation already in progress"
|
110
|
+
else
|
111
|
+
@r = PostgresPR::Connection::Result.new
|
112
|
+
@e = []
|
113
|
+
@pending_query = d
|
114
|
+
send_data PostgresPR::Query.dump(sql)
|
115
|
+
end
|
116
|
+
|
117
|
+
d
|
118
|
+
end
|
119
|
+
|
120
|
+
def receive_data(data)
|
121
|
+
@data << data
|
122
|
+
while @data.length >= 5
|
123
|
+
pktlen = @data[1...5].unpack("N").first
|
124
|
+
if @data.length >= (1 + pktlen)
|
125
|
+
pkt = @data.slice!(0...(1+pktlen))
|
126
|
+
m = StringIO.open( pkt, "r" ) {|io| PostgresPR::Message.read( io ) }
|
127
|
+
if @pending_conn
|
128
|
+
dispatch_conn_message m
|
129
|
+
elsif @pending_query
|
130
|
+
dispatch_query_message m
|
131
|
+
else
|
132
|
+
raise "Unexpected message from database"
|
133
|
+
end
|
134
|
+
else
|
135
|
+
break # very important, break out of the while
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Cloned and modified from the postgres-pr.
|
141
|
+
def dispatch_conn_message(msg)
|
142
|
+
case msg
|
143
|
+
when AuthentificationClearTextPassword
|
144
|
+
raise ArgumentError, "no password specified" if @password.nil?
|
145
|
+
send_data PasswordMessage.new(@password).dump
|
146
|
+
|
147
|
+
when AuthentificationCryptPassword
|
148
|
+
raise ArgumentError, "no password specified" if @password.nil?
|
149
|
+
send_data PasswordMessage.new(@password.crypt(msg.salt)).dump
|
150
|
+
|
151
|
+
when AuthentificationMD5Password
|
152
|
+
raise ArgumentError, "no password specified" if @password.nil?
|
153
|
+
require 'digest/md5'
|
154
|
+
|
155
|
+
m = Digest::MD5.hexdigest(@password + @user)
|
156
|
+
m = Digest::MD5.hexdigest(m + msg.salt)
|
157
|
+
m = 'md5' + m
|
158
|
+
send_data PasswordMessage.new(m).dump
|
159
|
+
|
160
|
+
when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential
|
161
|
+
raise "unsupported authentification"
|
162
|
+
|
163
|
+
when AuthentificationOk
|
164
|
+
when ErrorResponse
|
165
|
+
raise msg.field_values.join("\t")
|
166
|
+
when NoticeResponse
|
167
|
+
@notice_processor.call(msg) if @notice_processor
|
168
|
+
when ParameterStatus
|
169
|
+
@params[msg.key] = msg.value
|
170
|
+
when BackendKeyData
|
171
|
+
# TODO
|
172
|
+
#p msg
|
173
|
+
when ReadyForQuery
|
174
|
+
# TODO: use transaction status
|
175
|
+
pc,@pending_conn = @pending_conn,nil
|
176
|
+
pc.succeed true
|
177
|
+
else
|
178
|
+
raise "unhandled message type"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Cloned and modified from the postgres-pr.
|
183
|
+
def dispatch_query_message(msg)
|
184
|
+
case msg
|
185
|
+
when DataRow
|
186
|
+
@r.rows << msg.columns
|
187
|
+
when CommandComplete
|
188
|
+
@r.cmd_tag = msg.cmd_tag
|
189
|
+
when ReadyForQuery
|
190
|
+
pq,@pending_query = @pending_query,nil
|
191
|
+
pq.succeed @e.size == 0, @r, @e
|
192
|
+
when RowDescription
|
193
|
+
@r.fields = msg.fields
|
194
|
+
when CopyInResponse
|
195
|
+
when CopyOutResponse
|
196
|
+
when EmptyQueryResponse
|
197
|
+
when ErrorResponse
|
198
|
+
@e << msg.field_values[2]
|
199
|
+
when NoticeResponse
|
200
|
+
@notice_processor.call(msg) if @notice_processor
|
201
|
+
when ParameterStatus
|
202
|
+
else
|
203
|
+
# TODO
|
204
|
+
puts "Unknown Postgres message: #{msg}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'logger'
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
gem 'activerecord', '>= 2.3.5'
|
7
|
+
require 'active_record'
|
8
|
+
|
9
|
+
RAILS_ENV='test'
|
10
|
+
|
11
|
+
ActiveRecord::Base.configurations = YAML::load(ERB.new(File.read(File.join(File.dirname(__FILE__), 'database.yml'))).result)
|
12
|
+
ActiveRecord::Base.default_timezone = :utc
|
13
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
14
|
+
ActiveRecord::Base.logger.level = Logger::INFO
|
15
|
+
ActiveRecord::Base.pluralize_table_names = false
|
16
|
+
ActiveRecord::Base.time_zone_aware_attributes = true
|
17
|
+
Time.zone = 'UTC'
|
18
|
+
|
19
|
+
require 'eventmachine'
|
20
|
+
require 'test/unit'
|
21
|
+
|
22
|
+
class Site < ActiveRecord::Base
|
23
|
+
set_table_name 'site'
|
24
|
+
end
|
25
|
+
|
26
|
+
class TestDatabase < Test::Unit::TestCase
|
27
|
+
def test_live_server
|
28
|
+
EM.run do
|
29
|
+
Fiber.new do
|
30
|
+
ActiveRecord::Base.establish_connection
|
31
|
+
|
32
|
+
result = ActiveRecord::Base.connection.query('select id, domain_name from site')
|
33
|
+
assert result
|
34
|
+
assert_equal 3, result.size
|
35
|
+
|
36
|
+
result = Site.all
|
37
|
+
assert result
|
38
|
+
assert_equal 3, result.size
|
39
|
+
|
40
|
+
result = Site.find(1)
|
41
|
+
assert_equal 1, result.id
|
42
|
+
assert_equal 'somedomain.com', result.domain_name
|
43
|
+
end.resume
|
44
|
+
|
45
|
+
EM.add_timer(1) do
|
46
|
+
EM.stop
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em_postgresql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mike Perham
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-03 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: postgres-pr
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 6
|
30
|
+
- 1
|
31
|
+
version: 0.6.1
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: eventmachine
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 12
|
44
|
+
- 10
|
45
|
+
version: 0.12.10
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description: An ActiveRecord driver for using Postgresql with EventMachine
|
49
|
+
email: mperham@gmail.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- VERSION
|
60
|
+
- lib/active_record/connection_adapters/em_postgresql_adapter.rb
|
61
|
+
- lib/active_record/patches.rb
|
62
|
+
- lib/fiber_pool.rb
|
63
|
+
- lib/postgres_connection.rb
|
64
|
+
- test/database.yml
|
65
|
+
- test/test_database.rb
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://github.com/mperham/em_postgresql
|
68
|
+
licenses: []
|
69
|
+
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options:
|
72
|
+
- --charset=UTF-8
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.6
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: An ActiveRecord driver for using Postgresql with EventMachine
|
96
|
+
test_files:
|
97
|
+
- test/test_database.rb
|