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