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.
- data/CHANGELOG +13 -0
- data/COPYING +18 -0
- data/README +151 -0
- data/Rakefile +96 -0
- data/lib/sequel.rb +13 -0
- data/lib/sequel/connection_pool.rb +65 -0
- data/lib/sequel/core_ext.rb +9 -0
- data/lib/sequel/database.rb +119 -0
- data/lib/sequel/dataset.rb +357 -0
- data/lib/sequel/model.rb +237 -0
- data/lib/sequel/postgres.rb +396 -0
- data/lib/sequel/schema.rb +163 -0
- data/lib/sequel/sqlite.rb +112 -0
- data/spec/database_spec.rb +18 -0
- data/spec/dataset_spec.rb +124 -0
- data/spec/postgres_spec.rb +6 -0
- metadata +85 -0
@@ -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
|
+
|