sequel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,396 @@
1
+ require 'postgres'
2
+
3
+ class PGconn
4
+ # the pure-ruby postgres adapter does not have a quote method.
5
+ unless methods.include?('quote')
6
+ def self.quote(obj)
7
+ case obj
8
+ when true: 't'
9
+ when false: 'f'
10
+ when nil: 'NULL'
11
+ when String: "'#{obj}'"
12
+ else obj.to_s
13
+ end
14
+ end
15
+ end
16
+
17
+ def connected?
18
+ status == PGconn::CONNECTION_OK
19
+ end
20
+
21
+ SQL_BEGIN = 'BEGIN'.freeze
22
+ SQL_COMMIT = 'COMMIT'.freeze
23
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
24
+
25
+ def execute(sql)
26
+ begin
27
+ # ServerSide.info(sql)
28
+ async_exec(sql)
29
+ rescue PGError => e
30
+ unless connected?
31
+ # ServerSide.warn('Reconnecting to Postgres server')
32
+ reset
33
+ async_exec(sql)
34
+ else
35
+ p sql
36
+ p e
37
+ raise e
38
+ end
39
+ end
40
+ end
41
+
42
+ attr_reader :transaction_in_progress
43
+
44
+ def transaction
45
+ if @transaction_in_progress
46
+ return yield
47
+ end
48
+ # ServerSide.info('BEGIN')
49
+ async_exec(SQL_BEGIN)
50
+ begin
51
+ @transaction_in_progress = true
52
+ result = yield
53
+ # ServerSide.info('COMMIT')
54
+ async_exec(SQL_COMMIT)
55
+ result
56
+ rescue => e
57
+ # ServerSide.info('ROLLBACK')
58
+ async_exec(SQL_ROLLBACK)
59
+ raise e
60
+ ensure
61
+ @transaction_in_progress = nil
62
+ end
63
+ end
64
+ end
65
+
66
+ class String
67
+ def postgres_to_bool
68
+ if self == 't'
69
+ true
70
+ elsif self == 'f'
71
+ false
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ TIME_REGEXP = /(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})/
78
+
79
+ def postgres_to_time
80
+ if self =~ TIME_REGEXP
81
+ Time.local($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
82
+ else
83
+ nil
84
+ end
85
+ end
86
+ end
87
+
88
+ module Sequel
89
+ module Postgres
90
+ PG_TYPES = {
91
+ 16 => :postgres_to_bool,
92
+ 20 => :to_i,
93
+ 21 => :to_i,
94
+ 22 => :to_i,
95
+ 23 => :to_i,
96
+ 700 => :to_f,
97
+ 701 => :to_f,
98
+ 1114 => :postgres_to_time
99
+ }
100
+
101
+ class Database < Sequel::Database
102
+ set_adapter_scheme :postgres
103
+
104
+ attr_reader :pool
105
+
106
+ def initialize(opts = {})
107
+ super
108
+ @pool = ConnectionPool.new(@opts[:max_connections] || 4) do
109
+ PGconn.connect(
110
+ @opts[:host] || 'localhost',
111
+ @opts[:port] || 5432,
112
+ '', '',
113
+ @opts[:database] || 'reality_development',
114
+ @opts[:user] || 'postgres',
115
+ @opts[:password])
116
+ end
117
+ end
118
+
119
+
120
+ def dataset(opts = nil)
121
+ Postgres::Dataset.new(self, opts)
122
+ end
123
+
124
+ RELATION_QUERY = {:from => :pg_class, :select => :relname}.freeze
125
+ RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
126
+ SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
127
+
128
+
129
+ def tables
130
+ query(RELATION_QUERY).filter(RELATION_FILTER).map(:relname)
131
+ end
132
+
133
+ def locks
134
+ query.from("pg_class, pg_locks").
135
+ select("pg_class.relname, pg_locks.*").
136
+ filter("pg_class.relfilenode=pg_locks.relation")
137
+ end
138
+
139
+ def execute(sql)
140
+ @pool.hold {|conn| conn.execute(sql)}
141
+ end
142
+
143
+ def execute_and_forget(sql)
144
+ @pool.hold {|conn| conn.execute(sql).clear}
145
+ end
146
+
147
+ def synchronize(&block)
148
+ @pool.hold(&block)
149
+ end
150
+
151
+ def transaction(&block)
152
+ @pool.hold {|conn| conn.transaction(&block)}
153
+ end
154
+
155
+ def table_exists?(name)
156
+ from(:pg_class).filter(:relname => name, :relkind => 'r').count > 0
157
+ end
158
+ end
159
+
160
+ class Dataset < Sequel::Dataset
161
+ attr_reader :result, :fields
162
+
163
+ def literal(v)
164
+ case v
165
+ when Time: v.to_sql_timestamp
166
+ when Symbol: PGconn.quote(v.to_s)
167
+ when Array: v.empty? ? EMPTY_ARRAY : v.join(COMMA_SEPARATOR)
168
+ else
169
+ PGconn.quote(v)
170
+ end
171
+ end
172
+
173
+ LIKE = '%s ~ %s'.freeze
174
+ LIKE_CI = '%s ~* %s'.freeze
175
+
176
+ IN_ARRAY = '%s IN (%s)'.freeze
177
+ EMPTY_ARRAY = 'NULL'.freeze
178
+
179
+ def where_equal_condition(left, right)
180
+ case right
181
+ when Regexp:
182
+ (right.casefold? ? LIKE_CI : LIKE) %
183
+ [field_name(left), PGconn.quote(right.source)]
184
+ when Array:
185
+ IN_ARRAY % [field_name(left), literal(right)]
186
+ else
187
+ super
188
+ end
189
+ end
190
+
191
+ def each(opts = nil, &block)
192
+ query_each(select_sql(opts), true, &block)
193
+ self
194
+ end
195
+
196
+ LIMIT_1 = {:limit => 1}.freeze
197
+
198
+ def first(opts = nil)
199
+ opts = opts ? opts.merge(LIMIT_1) : LIMIT_1
200
+ query_first(select_sql(opts), true)
201
+ end
202
+
203
+ def last(opts = nil)
204
+ raise RuntimeError, 'No order specified' unless
205
+ @opts[:order] || (opts && opts[:order])
206
+
207
+ opts = {:order => reverse_order(@opts[:order])}.
208
+ merge(opts ? opts.merge(LIMIT_1) : LIMIT_1)
209
+
210
+ query_first(select_sql(opts), true)
211
+ end
212
+
213
+ FOR_UPDATE = ' FOR UPDATE'.freeze
214
+ FOR_SHARE = ' FOR SHARE'.freeze
215
+
216
+ def select_sql(opts = nil)
217
+ row_lock_mode = opts ? opts[:lock] : @opts[:lock]
218
+ sql = super
219
+ case row_lock_mode
220
+ when :update : sql << FOR_UPDATE
221
+ when :share : sql << FOR_SHARE
222
+ end
223
+ sql
224
+ end
225
+
226
+ def for_update
227
+ dup_merge(:lock => :update)
228
+ end
229
+
230
+ def for_share
231
+ dup_merge(:lock => :share)
232
+ end
233
+
234
+ EXPLAIN = 'EXPLAIN '.freeze
235
+ QUERY_PLAN = 'QUERY PLAN'.to_sym
236
+
237
+ def explain(opts = nil)
238
+ analysis = []
239
+ query_each(select_sql(EXPLAIN + select_sql(opts))) do |r|
240
+ analysis << r[QUERY_PLAN]
241
+ end
242
+ analysis.join("\r\n")
243
+ end
244
+
245
+ LOCK = 'LOCK TABLE %s IN %s MODE;'.freeze
246
+
247
+ ACCESS_SHARE = 'ACCESS SHARE'.freeze
248
+ ROW_SHARE = 'ROW SHARE'.freeze
249
+ ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
250
+ SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
251
+ SHARE = 'SHARE'.freeze
252
+ SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
253
+ EXCLUSIVE = 'EXCLUSIVE'.freeze
254
+ ACCESS_EXCLUSIVE = 'ACCESS EXCLUSIVE'.freeze
255
+
256
+ # Locks the table with the specified mode.
257
+ def lock(mode, &block)
258
+ sql = LOCK % [@opts[:from], mode]
259
+ @db.synchronize do
260
+ if block # perform locking inside a transaction and yield to block
261
+ @db.transaction {@db.execute_and_forget(sql); yield}
262
+ else
263
+ @db.execute_and_forget(sql) # lock without a transaction
264
+ self
265
+ end
266
+ end
267
+ end
268
+
269
+ def count(opts = nil)
270
+ query_single_value(count_sql(opts)).to_i
271
+ end
272
+
273
+ SELECT_LASTVAL = ';SELECT lastval()'.freeze
274
+
275
+ def insert(values = nil, opts = nil)
276
+ @db.execute_and_forget(insert_sql(values, opts))
277
+ query_single_value(SELECT_LASTVAL).to_i
278
+ end
279
+
280
+ def update(values, opts = nil)
281
+ @db.synchronize do
282
+ result = @db.execute(update_sql(values))
283
+ begin
284
+ affected = result.cmdtuples
285
+ ensure
286
+ result.clear
287
+ end
288
+ affected
289
+ end
290
+ end
291
+
292
+ def delete(opts = nil)
293
+ @db.synchronize do
294
+ result = @db.execute(delete_sql(opts))
295
+ begin
296
+ affected = result.cmdtuples
297
+ ensure
298
+ result.clear
299
+ end
300
+ affected
301
+ end
302
+ end
303
+
304
+ def query_all(sql, use_record_class = false)
305
+ @db.synchronize do
306
+ result = @db.execute(sql)
307
+ begin
308
+ conv = row_converter(result, use_record_class)
309
+ all = []
310
+ result.each {|r| all << conv[r]}
311
+ ensure
312
+ result.clear
313
+ end
314
+ all
315
+ end
316
+ end
317
+
318
+ def query_each(sql, use_record_class = false)
319
+ @db.synchronize do
320
+ result = @db.execute(sql)
321
+ begin
322
+ conv = row_converter(result, use_record_class)
323
+ result.each {|r| yield conv[r]}
324
+ ensure
325
+ result.clear
326
+ end
327
+ end
328
+ end
329
+
330
+ def query_first(sql, use_record_class = false)
331
+ @db.synchronize do
332
+ result = @db.execute(sql)
333
+ begin
334
+ row = nil
335
+ conv = row_converter(result, use_record_class)
336
+ result.each {|r| row = conv.call(r)}
337
+ ensure
338
+ result.clear
339
+ end
340
+ row
341
+ end
342
+ end
343
+
344
+ def query_single_value(sql)
345
+ @db.synchronize do
346
+ result = @db.execute(sql)
347
+ begin
348
+ value = result.getvalue(0, 0)
349
+ ensure
350
+ result.clear
351
+ end
352
+ value
353
+ end
354
+ end
355
+
356
+ COMMA = ','.freeze
357
+
358
+ @@converters_mutex = Mutex.new
359
+ @@converters = {}
360
+
361
+ def row_converter(result, use_record_class)
362
+ fields = result.fields.map {|s| s.to_sym}
363
+ types = (0..(result.num_fields - 1)).map {|idx| result.type(idx)}
364
+ klass = use_record_class ? @record_class : nil
365
+
366
+ # create result signature and memoize the converter
367
+ sig = fields.join(COMMA) + types.join(COMMA) + klass.to_s
368
+ @@converters_mutex.synchronize do
369
+ @@converters[sig] ||= compile_converter(fields, types, klass)
370
+ end
371
+ end
372
+
373
+ CONVERT = "lambda {|r| {%s}}".freeze
374
+ CONVERT_RECORD_CLASS = "lambda {|r| %2$s.new(%1$s)}".freeze
375
+
376
+ CONVERT_FIELD = '%s => r[%d]'.freeze
377
+ CONVERT_FIELD_TRANSLATE = '%s => ((t = r[%d]) ? t.%s : nil)'.freeze
378
+
379
+ def compile_converter(fields, types, klass)
380
+ used_fields = []
381
+ kvs = []
382
+ fields.each_with_index do |field, idx|
383
+ next if used_fields.include?(field)
384
+ used_fields << field
385
+
386
+ translate_fn = PG_TYPES[types[idx]]
387
+ kvs << (translate_fn ? CONVERT_FIELD_TRANSLATE : CONVERT_FIELD) %
388
+ [field.inspect, idx, translate_fn]
389
+ end
390
+ s = (klass ? CONVERT_RECORD_CLASS : CONVERT) %
391
+ [kvs.join(COMMA), klass]
392
+ eval(s)
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,163 @@
1
+ require 'rubygems'
2
+ require 'postgres'
3
+
4
+ module Sequel
5
+ class Schema
6
+ COMMA_SEPARATOR = ', '.freeze
7
+ COLUMN_DEF = '%s %s'.freeze
8
+ UNIQUE = ' UNIQUE'.freeze
9
+ NOT_NULL = ' NOT NULL'.freeze
10
+ DEFAULT = ' DEFAULT %s'.freeze
11
+ PRIMARY_KEY = ' PRIMARY KEY'.freeze
12
+ REFERENCES = ' REFERENCES %s'.freeze
13
+ ON_DELETE = ' ON DELETE %s'.freeze
14
+
15
+ RESTRICT = 'RESTRICT'.freeze
16
+ CASCADE = 'CASCADE'.freeze
17
+ NO_ACTION = 'NO ACTION'.freeze
18
+ SET_NULL = 'SET NULL'.freeze
19
+ SET_DEFAULT = 'SET DEFAULT'.freeze
20
+
21
+ TYPES = Hash.new {|h, k| k}
22
+ TYPES[:double] = 'double precision'
23
+
24
+ def self.on_delete_action(action)
25
+ case action
26
+ when :restrict: RESTRICT
27
+ when :cascade: CASCADE
28
+ when :set_null: SET_NULL
29
+ when :set_default: SET_DEFAULT
30
+ else NO_ACTION
31
+ end
32
+ end
33
+
34
+ def self.column_definition(column)
35
+ c = COLUMN_DEF % [column[:name], TYPES[column[:type]]]
36
+ c << UNIQUE if column[:unique]
37
+ c << NOT_NULL if column[:null] == false
38
+ c << DEFAULT % PGconn.quote(column[:default]) if column.include?(:default)
39
+ c << PRIMARY_KEY if column[:primary_key]
40
+ c << REFERENCES % column[:table] if column[:table]
41
+ c << ON_DELETE % on_delete_action(column[:on_delete]) if
42
+ column[:on_delete]
43
+ c
44
+ end
45
+
46
+ def self.create_table_column_list(columns)
47
+ columns.map {|c| column_definition(c)}.join(COMMA_SEPARATOR)
48
+ end
49
+
50
+ CREATE_INDEX = 'CREATE INDEX %s ON %s (%s);'.freeze
51
+ CREATE_UNIQUE_INDEX = 'CREATE UNIQUE INDEX %s ON %s (%s);'.freeze
52
+ INDEX_NAME = '%s_%s_index'.freeze
53
+ UNDERSCORE = '_'.freeze
54
+
55
+ def self.index_definition(table_name, index)
56
+ fields = index[:columns].join(COMMA_SEPARATOR)
57
+ index_name = index[:name] || INDEX_NAME %
58
+ [table_name, index[:columns].join(UNDERSCORE)]
59
+ (index[:unique] ? CREATE_UNIQUE_INDEX : CREATE_INDEX) %
60
+ [index_name, table_name, fields]
61
+ end
62
+
63
+ def self.create_indexes_sql(table_name, indexes)
64
+ indexes.map {|i| index_definition(table_name, i)}.join
65
+ end
66
+
67
+ CREATE_TABLE = "CREATE TABLE %s (%s);".freeze
68
+
69
+ def self.create_table_sql(name, columns, indexes = nil)
70
+ sql = CREATE_TABLE % [name, create_table_column_list(columns)]
71
+ sql << create_indexes_sql(name, indexes) if indexes && !indexes.empty?
72
+ sql
73
+ end
74
+
75
+ DROP_TABLE = "DROP TABLE %s CASCADE;".freeze
76
+
77
+ def self.drop_table_sql(name)
78
+ DROP_TABLE % name
79
+ end
80
+
81
+ class Generator
82
+ attr_reader :table_name
83
+
84
+ def initialize(table_name, &block)
85
+ @table_name = table_name
86
+ @primary_key = {:name => :id, :type => :serial, :primary_key => true}
87
+ @columns = []
88
+ @indexes = []
89
+ instance_eval(&block)
90
+ end
91
+
92
+ def primary_key(name, type = nil, opts = nil)
93
+ @primary_key = {
94
+ :name => name,
95
+ :type => type || :serial,
96
+ :primary_key => true
97
+ }.merge(opts || {})
98
+ end
99
+
100
+ def primary_key_name
101
+ @primary_key && @primary_key[:name]
102
+ end
103
+
104
+ def column(name, type, opts = nil)
105
+ @columns << {:name => name, :type => type}.merge(opts || {})
106
+ end
107
+
108
+ def foreign_key(name, opts)
109
+ @columns << {:name => name, :type => :integer}.merge(opts || {})
110
+ end
111
+
112
+ def has_column?(name)
113
+ @columns.each {|c| return true if c[:name] == name}
114
+ false
115
+ end
116
+
117
+ def index(columns, opts = nil)
118
+ columns = [columns] unless columns.is_a?(Array)
119
+ @indexes << {:columns => columns}.merge(opts || {})
120
+ end
121
+
122
+ def create_sql
123
+ if @primary_key && !has_column?(@primary_key[:name])
124
+ @columns.unshift(@primary_key)
125
+ end
126
+ Schema.create_table_sql(@table_name, @columns, @indexes)
127
+ end
128
+
129
+ def drop_sql
130
+ Schema.drop_table_sql(@table_name)
131
+ end
132
+ end
133
+
134
+ attr_reader :instructions
135
+
136
+ def initialize(&block)
137
+ @instructions = []
138
+ instance_eval(&block) if block
139
+ end
140
+
141
+ def create_table(table_name, &block)
142
+ @instructions << Generator.new(table_name, &block)
143
+ end
144
+
145
+ def create(db)
146
+ @instructions.each do |s|
147
+ db.execute(s.create_sql)
148
+ end
149
+ end
150
+
151
+ def drop(db)
152
+ @instructions.reverse_each do |s|
153
+ db.execute(s.drop_sql) if db.table_exists?(s.table_name)
154
+ end
155
+ end
156
+
157
+ def recreate(db)
158
+ drop(db)
159
+ create(db)
160
+ end
161
+ end
162
+ end
163
+