sequel 0.0.1

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,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
+