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.
@@ -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
+
@@ -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
@@ -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
@@ -0,0 +1,10 @@
1
+ test:
2
+ adapter: em_postgresql
3
+ host: localhost
4
+ username: mike
5
+ password: password
6
+ database: onespot_test
7
+ schema_search_path: bdu,public
8
+ statement_timeout: 60
9
+ encoding: UTF8
10
+ port: 5432
@@ -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