sequel 2.2.0 → 2.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.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- data/spec/inflector_spec.rb +0 -34
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Postgres
|
|
3
|
+
CONVERTED_EXCEPTIONS = []
|
|
4
|
+
|
|
5
|
+
module AdapterMethods
|
|
6
|
+
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
|
7
|
+
SELECT_PK = <<-end_sql
|
|
8
|
+
SELECT pg_attribute.attname
|
|
9
|
+
FROM pg_class, pg_attribute, pg_index
|
|
10
|
+
WHERE pg_class.oid = pg_attribute.attrelid AND
|
|
11
|
+
pg_class.oid = pg_index.indrelid AND
|
|
12
|
+
pg_index.indkey[0] = pg_attribute.attnum AND
|
|
13
|
+
pg_index.indisprimary = 't' AND
|
|
14
|
+
pg_class.relname = '%s'
|
|
15
|
+
end_sql
|
|
16
|
+
SELECT_PK_AND_CUSTOM_SEQUENCE = <<-end_sql
|
|
17
|
+
SELECT attr.attname,
|
|
18
|
+
CASE
|
|
19
|
+
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
|
20
|
+
substr(split_part(def.adsrc, '''', 2),
|
|
21
|
+
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
|
22
|
+
ELSE split_part(def.adsrc, '''', 2)
|
|
23
|
+
END
|
|
24
|
+
FROM pg_class t
|
|
25
|
+
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
|
26
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
|
27
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
|
28
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
|
29
|
+
WHERE t.oid = '%s'::regclass
|
|
30
|
+
AND cons.contype = 'p'
|
|
31
|
+
AND def.adsrc ~* 'nextval'
|
|
32
|
+
end_sql
|
|
33
|
+
SELECT_PK_AND_SERIAL_SEQUENCE = <<-end_sql
|
|
34
|
+
SELECT attr.attname, name.nspname, seq.relname
|
|
35
|
+
FROM pg_class seq, pg_attribute attr, pg_depend dep,
|
|
36
|
+
pg_namespace name, pg_constraint cons
|
|
37
|
+
WHERE seq.oid = dep.objid
|
|
38
|
+
AND seq.relnamespace = name.oid
|
|
39
|
+
AND seq.relkind = 'S'
|
|
40
|
+
AND attr.attrelid = dep.refobjid
|
|
41
|
+
AND attr.attnum = dep.refobjsubid
|
|
42
|
+
AND attr.attrelid = cons.conrelid
|
|
43
|
+
AND attr.attnum = cons.conkey[1]
|
|
44
|
+
AND cons.contype = 'p'
|
|
45
|
+
AND dep.refobjid = '%s'::regclass
|
|
46
|
+
end_sql
|
|
47
|
+
|
|
48
|
+
attr_accessor :transaction_depth
|
|
49
|
+
|
|
50
|
+
def last_insert_id(table)
|
|
51
|
+
@table_sequences ||= {}
|
|
52
|
+
if !@table_sequences.include?(table)
|
|
53
|
+
pkey_and_seq = pkey_and_sequence(table)
|
|
54
|
+
if pkey_and_seq
|
|
55
|
+
@table_sequences[table] = pkey_and_seq[1]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
if seq = @table_sequences[table]
|
|
59
|
+
execute(SELECT_CURRVAL % seq) do |r|
|
|
60
|
+
val = result_set_values(r, 0)
|
|
61
|
+
val.to_i if val
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def pkey_and_sequence(table)
|
|
67
|
+
execute(SELECT_PK_AND_SERIAL_SEQUENCE % table) do |r|
|
|
68
|
+
vals = result_set_values(r, 2, 2)
|
|
69
|
+
return vals if vals
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
execute(SELECT_PK_AND_CUSTOM_SEQUENCE % table) do |r|
|
|
73
|
+
result_set_values(r, 0, 1)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def primary_key(table)
|
|
78
|
+
execute(SELECT_PK % table) do |r|
|
|
79
|
+
result_set_values(r, 0)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
module DatabaseMethods
|
|
85
|
+
RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
|
|
86
|
+
RELATION_QUERY = {:from => [:pg_class], :select => [:relname]}.freeze
|
|
87
|
+
RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
|
|
88
|
+
SQL_BEGIN = 'BEGIN'.freeze
|
|
89
|
+
SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
|
|
90
|
+
SQL_COMMIT = 'COMMIT'.freeze
|
|
91
|
+
SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
|
|
92
|
+
SQL_ROLLBACK = 'ROLLBACK'.freeze
|
|
93
|
+
SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
|
|
94
|
+
SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
|
|
95
|
+
|
|
96
|
+
def drop_table_sql(name)
|
|
97
|
+
"DROP TABLE #{name} CASCADE"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def execute_insert(sql, table, values)
|
|
101
|
+
begin
|
|
102
|
+
log_info(sql)
|
|
103
|
+
@pool.hold do |conn|
|
|
104
|
+
conn.execute(sql)
|
|
105
|
+
insert_result(conn, table, values)
|
|
106
|
+
end
|
|
107
|
+
rescue => e
|
|
108
|
+
log_info(e.message)
|
|
109
|
+
raise convert_pgerror(e)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def index_definition_sql(table_name, index)
|
|
114
|
+
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
|
115
|
+
expr = literal(Array(index[:columns]))
|
|
116
|
+
unique = "UNIQUE " if index[:unique]
|
|
117
|
+
index_type = index[:type]
|
|
118
|
+
filter = index[:where] || index[:filter]
|
|
119
|
+
filter = " WHERE #{filter_expr(filter)}" if filter
|
|
120
|
+
case index_type
|
|
121
|
+
when :full_text
|
|
122
|
+
lang = index[:language] ? "#{literal(index[:language])}, " : ""
|
|
123
|
+
cols = index[:columns].map {|c| literal(c)}.join(" || ")
|
|
124
|
+
expr = "(to_tsvector(#{lang}#{cols}))"
|
|
125
|
+
index_type = :gin
|
|
126
|
+
when :spatial
|
|
127
|
+
index_type = :gist
|
|
128
|
+
end
|
|
129
|
+
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def insert_result(conn, table, values)
|
|
133
|
+
begin
|
|
134
|
+
result = conn.last_insert_id(table)
|
|
135
|
+
return result if result
|
|
136
|
+
rescue Exception => e
|
|
137
|
+
convert_pgerror(e) unless RE_CURRVAL_ERROR.match(e.message)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
case values
|
|
141
|
+
when Hash
|
|
142
|
+
values[primary_key_for_table(conn, table)]
|
|
143
|
+
when Array
|
|
144
|
+
values.first
|
|
145
|
+
else
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def locks
|
|
151
|
+
dataset.from("pg_class, pg_locks").
|
|
152
|
+
select("pg_class.relname, pg_locks.*").
|
|
153
|
+
filter("pg_class.relfilenode=pg_locks.relation")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def primary_key_for_table(conn, table)
|
|
157
|
+
@primary_keys ||= {}
|
|
158
|
+
@primary_keys[table] ||= conn.primary_key(table)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def serial_primary_key_options
|
|
162
|
+
{:primary_key => true, :type => :serial}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def server_version
|
|
166
|
+
return @server_version if @server_version
|
|
167
|
+
@server_version = pool.hold do |conn|
|
|
168
|
+
(conn.server_version rescue nil) if conn.respond_to?(:server_version)
|
|
169
|
+
end
|
|
170
|
+
unless @server_version
|
|
171
|
+
m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
|
|
172
|
+
@server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
|
|
173
|
+
end
|
|
174
|
+
@server_version
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def tables
|
|
178
|
+
dataset(RELATION_QUERY).filter(RELATION_FILTER).map {|r| r[:relname].to_sym}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def transaction
|
|
182
|
+
@pool.hold do |conn|
|
|
183
|
+
conn.transaction_depth = 0 if conn.transaction_depth.nil?
|
|
184
|
+
if conn.transaction_depth > 0
|
|
185
|
+
log_info(SQL_SAVEPOINT % conn.transaction_depth)
|
|
186
|
+
conn.execute(SQL_SAVEPOINT % conn.transaction_depth)
|
|
187
|
+
else
|
|
188
|
+
log_info(SQL_BEGIN)
|
|
189
|
+
conn.execute(SQL_BEGIN)
|
|
190
|
+
end
|
|
191
|
+
begin
|
|
192
|
+
conn.transaction_depth += 1
|
|
193
|
+
yield conn
|
|
194
|
+
rescue ::Exception => e
|
|
195
|
+
if conn.transaction_depth > 1
|
|
196
|
+
log_info(SQL_ROLLBACK_TO_SAVEPOINT % [conn.transaction_depth - 1])
|
|
197
|
+
conn.execute(SQL_ROLLBACK_TO_SAVEPOINT % [conn.transaction_depth - 1])
|
|
198
|
+
else
|
|
199
|
+
log_info(SQL_ROLLBACK)
|
|
200
|
+
conn.execute(SQL_ROLLBACK) rescue nil
|
|
201
|
+
end
|
|
202
|
+
raise convert_pgerror(e) unless Error::Rollback === e
|
|
203
|
+
ensure
|
|
204
|
+
unless e
|
|
205
|
+
begin
|
|
206
|
+
if conn.transaction_depth < 2
|
|
207
|
+
log_info(SQL_COMMIT)
|
|
208
|
+
conn.execute(SQL_COMMIT)
|
|
209
|
+
else
|
|
210
|
+
log_info(SQL_RELEASE_SAVEPOINT % [conn.transaction_depth - 1])
|
|
211
|
+
conn.execute(SQL_RELEASE_SAVEPOINT % [conn.transaction_depth - 1])
|
|
212
|
+
end
|
|
213
|
+
rescue => e
|
|
214
|
+
log_info(e.message)
|
|
215
|
+
raise convert_pgerror(e)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
conn.transaction_depth -= 1
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def convert_pgerror(e)
|
|
226
|
+
e.is_one_of?(*CONVERTED_EXCEPTIONS) ? Error.new(e.message) : e
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def schema_ds_filter(table_name, opts)
|
|
230
|
+
filt = super
|
|
231
|
+
# Restrict it to the given or public schema, unless specifically requesting :schema = nil
|
|
232
|
+
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:schema] || 'public'}) if opts[:schema] || !opts.include?(:schema)
|
|
233
|
+
filt
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
module DatasetMethods
|
|
238
|
+
ACCESS_SHARE = 'ACCESS SHARE'.freeze
|
|
239
|
+
ACCESS_EXCLUSIVE = 'ACCESS EXCLUSIVE'.freeze
|
|
240
|
+
BOOL_FALSE = 'false'.freeze
|
|
241
|
+
BOOL_TRUE = 'true'.freeze
|
|
242
|
+
COMMA_SEPARATOR = ', '.freeze
|
|
243
|
+
EXCLUSIVE = 'EXCLUSIVE'.freeze
|
|
244
|
+
EXPLAIN = 'EXPLAIN '.freeze
|
|
245
|
+
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
|
246
|
+
FOR_SHARE = ' FOR SHARE'.freeze
|
|
247
|
+
FOR_UPDATE = ' FOR UPDATE'.freeze
|
|
248
|
+
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
|
249
|
+
PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
|
|
250
|
+
QUERY_PLAN = 'QUERY PLAN'.to_sym
|
|
251
|
+
ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
|
|
252
|
+
ROW_SHARE = 'ROW SHARE'.freeze
|
|
253
|
+
SHARE = 'SHARE'.freeze
|
|
254
|
+
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
|
255
|
+
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
|
256
|
+
|
|
257
|
+
def analyze(opts = nil)
|
|
258
|
+
analysis = []
|
|
259
|
+
fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
|
|
260
|
+
analysis << r[QUERY_PLAN]
|
|
261
|
+
end
|
|
262
|
+
analysis.join("\r\n")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def explain(opts = nil)
|
|
266
|
+
analysis = []
|
|
267
|
+
fetch_rows(EXPLAIN + select_sql(opts)) do |r|
|
|
268
|
+
analysis << r[QUERY_PLAN]
|
|
269
|
+
end
|
|
270
|
+
analysis.join("\r\n")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def for_share
|
|
274
|
+
clone(:lock => :share)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def for_update
|
|
278
|
+
clone(:lock => :update)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def full_text_search(cols, terms, opts = {})
|
|
282
|
+
lang = opts[:language] ? "#{literal(opts[:language])}, " : ""
|
|
283
|
+
cols = cols.is_a?(Array) ? cols.map {|c| literal(c)}.join(" || ") : literal(cols)
|
|
284
|
+
terms = terms.is_a?(Array) ? literal(terms.join(" | ")) : literal(terms)
|
|
285
|
+
filter("to_tsvector(#{lang}#{cols}) @@ to_tsquery(#{lang}#{terms})")
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def insert(*values)
|
|
289
|
+
@db.execute_insert(insert_sql(*values), source_list(@opts[:from]),
|
|
290
|
+
values.size == 1 ? values.first : values)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def literal(v)
|
|
294
|
+
case v
|
|
295
|
+
when LiteralString
|
|
296
|
+
v
|
|
297
|
+
when String
|
|
298
|
+
db.synchronize{|c| "'#{SQL::Blob === v ? c.escape_bytea(v) : c.escape_string(v)}'"}
|
|
299
|
+
when Time
|
|
300
|
+
"#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
|
|
301
|
+
when DateTime
|
|
302
|
+
"#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d", (v.sec_fraction * 86400000000).to_i)}'"
|
|
303
|
+
when TrueClass
|
|
304
|
+
BOOL_TRUE
|
|
305
|
+
when FalseClass
|
|
306
|
+
BOOL_FALSE
|
|
307
|
+
else
|
|
308
|
+
super
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Locks the table with the specified mode.
|
|
313
|
+
def lock(mode, &block)
|
|
314
|
+
sql = LOCK % [source_list(@opts[:from]), mode]
|
|
315
|
+
@db.synchronize do
|
|
316
|
+
if block # perform locking inside a transaction and yield to block
|
|
317
|
+
@db.transaction {@db.execute(sql); yield}
|
|
318
|
+
else
|
|
319
|
+
@db.execute(sql) # lock without a transaction
|
|
320
|
+
self
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def multi_insert_sql(columns, values)
|
|
326
|
+
return super if @db.server_version < 80200
|
|
327
|
+
|
|
328
|
+
# postgresql 8.2 introduces support for multi-row insert
|
|
329
|
+
columns = column_list(columns)
|
|
330
|
+
values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
|
|
331
|
+
["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def quoted_identifier(c)
|
|
335
|
+
"\"#{c}\""
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def select_sql(opts = nil)
|
|
339
|
+
row_lock_mode = opts ? opts[:lock] : @opts[:lock]
|
|
340
|
+
sql = super
|
|
341
|
+
case row_lock_mode
|
|
342
|
+
when :update
|
|
343
|
+
sql << FOR_UPDATE
|
|
344
|
+
when :share
|
|
345
|
+
sql << FOR_SHARE
|
|
346
|
+
end
|
|
347
|
+
sql
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module SQLite
|
|
3
|
+
module DatabaseMethods
|
|
4
|
+
AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
|
|
5
|
+
SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/
|
|
6
|
+
SYNCHRONOUS = {'0' => :off, '1' => :normal, '2' => :full}.freeze
|
|
7
|
+
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
|
8
|
+
TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
|
|
9
|
+
|
|
10
|
+
def alter_table_sql(table, op)
|
|
11
|
+
case op[:op]
|
|
12
|
+
when :add_column
|
|
13
|
+
super
|
|
14
|
+
when :add_index
|
|
15
|
+
index_definition_sql(table, op)
|
|
16
|
+
when :drop_column
|
|
17
|
+
columns_str = (schema_parse_table(table, {}).map{|c| c[0]} - Array(op[:name])).join(",")
|
|
18
|
+
["BEGIN TRANSACTION",
|
|
19
|
+
"CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
|
|
20
|
+
"INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table}",
|
|
21
|
+
"DROP TABLE #{table}",
|
|
22
|
+
"CREATE TABLE #{table}(#{columns_str})",
|
|
23
|
+
"INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup",
|
|
24
|
+
"DROP TABLE #{table}_backup",
|
|
25
|
+
"COMMIT"]
|
|
26
|
+
else
|
|
27
|
+
raise Error, "Unsupported ALTER TABLE operation"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def auto_vacuum
|
|
32
|
+
AUTO_VACUUM[pragma_get(:auto_vacuum).to_s]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def auto_vacuum=(value)
|
|
36
|
+
value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
|
37
|
+
pragma_set(:auto_vacuum, value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pragma_get(name)
|
|
41
|
+
self["PRAGMA #{name}"].single_value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def pragma_set(name, value)
|
|
45
|
+
execute_ddl("PRAGMA #{name} = #{value}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def serial_primary_key_options
|
|
49
|
+
{:primary_key => true, :type => :integer, :auto_increment => true}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def synchronous
|
|
53
|
+
SYNCHRONOUS[pragma_get(:synchronous).to_s]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def synchronous=(value)
|
|
57
|
+
value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
|
58
|
+
pragma_set(:synchronous, value)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tables
|
|
62
|
+
self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def temp_store
|
|
66
|
+
TEMP_STORE[pragma_get(:temp_store).to_s]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def temp_store=(value)
|
|
70
|
+
value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
|
71
|
+
pragma_set(:temp_store, value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def schema_parse_table(table_name, opts)
|
|
77
|
+
rows = self["PRAGMA table_info(?)", table_name].collect do |row|
|
|
78
|
+
row.delete(:cid)
|
|
79
|
+
row[:column] = row.delete(:name)
|
|
80
|
+
row[:allow_null] = row.delete(:notnull).to_i == 0 ? 'YES' : 'NO'
|
|
81
|
+
row[:default] = row.delete(:dflt_value)
|
|
82
|
+
row[:primary_key] = row.delete(:pk).to_i == 1 ? true : false
|
|
83
|
+
row[:db_type] = row.delete(:type)
|
|
84
|
+
if m = SCHEMA_TYPE_RE.match(row[:db_type])
|
|
85
|
+
row[:db_type] = m[1]
|
|
86
|
+
row[:max_chars] = m[2].to_i
|
|
87
|
+
else
|
|
88
|
+
row[:max_chars] = nil
|
|
89
|
+
end
|
|
90
|
+
row[:numeric_precision] = nil
|
|
91
|
+
row
|
|
92
|
+
end
|
|
93
|
+
schema_parse_rows(rows)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def schema_parse_tables(opts)
|
|
97
|
+
schemas = {}
|
|
98
|
+
tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
|
|
99
|
+
schemas
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
module DatasetMethods
|
|
104
|
+
def complex_expression_sql(op, args)
|
|
105
|
+
case op
|
|
106
|
+
when :~, :'!~', :'~*', :'!~*'
|
|
107
|
+
raise Error, "SQLite does not support pattern matching via regular expressions"
|
|
108
|
+
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
109
|
+
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
|
110
|
+
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
|
111
|
+
else
|
|
112
|
+
super(op, args)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def delete(opts = nil)
|
|
117
|
+
# check if no filter is specified
|
|
118
|
+
unless (opts && opts[:where]) || @opts[:where]
|
|
119
|
+
@db.transaction do
|
|
120
|
+
unfiltered_count = count
|
|
121
|
+
@db.execute_dui delete_sql(opts)
|
|
122
|
+
unfiltered_count
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
@db.execute_dui delete_sql(opts)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def insert(*values)
|
|
130
|
+
@db.execute_insert insert_sql(*values)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def insert_sql(*values)
|
|
134
|
+
if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
|
|
135
|
+
"INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
|
|
136
|
+
else
|
|
137
|
+
super(*values)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def quoted_identifier(c)
|
|
142
|
+
"`#{c}`"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|