em_postgresql 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|