epugh-sequel 0.0.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/README.rdoc +652 -0
- data/VERSION.yml +4 -0
- data/bin/sequel +104 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +85 -0
- data/lib/sequel/adapters/db2.rb +132 -0
- data/lib/sequel/adapters/dbi.rb +101 -0
- data/lib/sequel/adapters/do.rb +197 -0
- data/lib/sequel/adapters/do/mysql.rb +38 -0
- data/lib/sequel/adapters/do/postgres.rb +92 -0
- data/lib/sequel/adapters/do/sqlite.rb +31 -0
- data/lib/sequel/adapters/firebird.rb +307 -0
- data/lib/sequel/adapters/informix.rb +75 -0
- data/lib/sequel/adapters/jdbc.rb +485 -0
- data/lib/sequel/adapters/jdbc/h2.rb +62 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel/adapters/mysql.rb +370 -0
- data/lib/sequel/adapters/odbc.rb +184 -0
- data/lib/sequel/adapters/openbase.rb +57 -0
- data/lib/sequel/adapters/oracle.rb +140 -0
- data/lib/sequel/adapters/postgres.rb +453 -0
- data/lib/sequel/adapters/shared/mssql.rb +93 -0
- data/lib/sequel/adapters/shared/mysql.rb +341 -0
- data/lib/sequel/adapters/shared/oracle.rb +62 -0
- data/lib/sequel/adapters/shared/postgres.rb +743 -0
- data/lib/sequel/adapters/shared/progress.rb +34 -0
- data/lib/sequel/adapters/shared/sqlite.rb +263 -0
- data/lib/sequel/adapters/sqlite.rb +243 -0
- data/lib/sequel/adapters/utils/date_format.rb +21 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
- data/lib/sequel/adapters/utils/unsupported.rb +62 -0
- data/lib/sequel/connection_pool.rb +258 -0
- data/lib/sequel/core.rb +204 -0
- data/lib/sequel/core_sql.rb +185 -0
- data/lib/sequel/database.rb +687 -0
- data/lib/sequel/database/schema_generator.rb +324 -0
- data/lib/sequel/database/schema_methods.rb +164 -0
- data/lib/sequel/database/schema_sql.rb +324 -0
- data/lib/sequel/dataset.rb +422 -0
- data/lib/sequel/dataset/convenience.rb +237 -0
- data/lib/sequel/dataset/prepared_statements.rb +220 -0
- data/lib/sequel/dataset/sql.rb +1105 -0
- data/lib/sequel/deprecated.rb +529 -0
- data/lib/sequel/exceptions.rb +44 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/sequel/extensions/inflector.rb +288 -0
- data/lib/sequel/extensions/pagination.rb +96 -0
- data/lib/sequel/extensions/pretty_table.rb +78 -0
- data/lib/sequel/extensions/query.rb +48 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +44 -0
- data/lib/sequel/migration.rb +212 -0
- data/lib/sequel/model.rb +142 -0
- data/lib/sequel/model/association_reflection.rb +263 -0
- data/lib/sequel/model/associations.rb +1024 -0
- data/lib/sequel/model/base.rb +911 -0
- data/lib/sequel/model/deprecated.rb +188 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +384 -0
- data/lib/sequel/model/errors.rb +37 -0
- data/lib/sequel/model/exceptions.rb +7 -0
- data/lib/sequel/model/inflections.rb +230 -0
- data/lib/sequel/model/plugins.rb +74 -0
- data/lib/sequel/object_graph.rb +230 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +373 -0
- data/lib/sequel/sql.rb +854 -0
- data/lib/sequel/version.rb +11 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +875 -0
- data/spec/adapters/oracle_spec.rb +272 -0
- data/spec/adapters/postgres_spec.rb +692 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +550 -0
- data/spec/core/connection_pool_spec.rb +526 -0
- data/spec/core/core_ext_spec.rb +156 -0
- data/spec/core/core_sql_spec.rb +528 -0
- data/spec/core/database_spec.rb +1214 -0
- data/spec/core/dataset_spec.rb +3513 -0
- data/spec/core/expression_filters_spec.rb +363 -0
- data/spec/core/migration_spec.rb +261 -0
- data/spec/core/object_graph_spec.rb +280 -0
- data/spec/core/pretty_table_spec.rb +58 -0
- data/spec/core/schema_generator_spec.rb +167 -0
- data/spec/core/schema_spec.rb +778 -0
- data/spec/core/spec_helper.rb +82 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/inflector_spec.rb +122 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/schema_spec.rb +111 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/extensions/spec_helper.rb +90 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/validation_class_methods_spec.rb +1054 -0
- data/spec/integration/dataset_test.rb +160 -0
- data/spec/integration/eager_loader_test.rb +683 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +183 -0
- data/spec/integration/spec_helper.rb +75 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +93 -0
- data/spec/model/associations_spec.rb +1780 -0
- data/spec/model/base_spec.rb +494 -0
- data/spec/model/caching_spec.rb +217 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1165 -0
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/model/model_spec.rb +588 -0
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/model/record_spec.rb +1243 -0
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +202 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Sequel.require %w'date_format unsupported', 'adapters/utils'
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module Progress
|
|
5
|
+
module DatabaseMethods
|
|
6
|
+
|
|
7
|
+
def dataset(opts = nil)
|
|
8
|
+
ds = super
|
|
9
|
+
ds.extend(DatasetMethods)
|
|
10
|
+
ds
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module DatasetMethods
|
|
15
|
+
include Dataset::UnsupportedIntersectExcept
|
|
16
|
+
include Dataset::SQLStandardDateFormat
|
|
17
|
+
|
|
18
|
+
SELECT_CLAUSE_ORDER = %w'limit distinct columns from join where group order having compounds'.freeze
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def select_clause_order
|
|
23
|
+
SELECT_CLAUSE_ORDER
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Progress uses TOP for limit, but it is only supported in Progress 10.
|
|
27
|
+
# The Progress adapter targets Progress 9, so it silently ignores the option.
|
|
28
|
+
def select_limit_sql(sql, opts)
|
|
29
|
+
raise(Error, "OFFSET not supported") if opts[:offset]
|
|
30
|
+
#sql << " TOP #{opts[:limit]}" if opts[:limit]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Sequel.require 'adapters/utils/unsupported'
|
|
2
|
+
|
|
3
|
+
module Sequel
|
|
4
|
+
module SQLite
|
|
5
|
+
module DatabaseMethods
|
|
6
|
+
AUTO_VACUUM = [:none, :full, :incremental].freeze
|
|
7
|
+
SYNCHRONOUS = [:off, :normal, :full].freeze
|
|
8
|
+
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
|
9
|
+
TEMP_STORE = [:default, :file, :memory].freeze
|
|
10
|
+
TYPES = Sequel::Database::TYPES.merge(Bignum=>'integer')
|
|
11
|
+
|
|
12
|
+
# Run all alter_table commands in a transaction. This is technically only
|
|
13
|
+
# needed for drop column.
|
|
14
|
+
def alter_table(name, generator=nil, &block)
|
|
15
|
+
transaction{super}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# SQLite supports limited table modification. You can add a column
|
|
19
|
+
# or an index. Dropping columns is supported by copying the table into
|
|
20
|
+
# a temporary table, dropping the table, and creating a new table without
|
|
21
|
+
# the column inside of a transaction.
|
|
22
|
+
def alter_table_sql(table, op)
|
|
23
|
+
case op[:op]
|
|
24
|
+
when :add_column, :add_index, :drop_index
|
|
25
|
+
super
|
|
26
|
+
when :drop_column
|
|
27
|
+
qt = quote_schema_table(table)
|
|
28
|
+
bt = quote_identifier(backup_table_name(qt.gsub('`', '')))
|
|
29
|
+
columns_str = dataset.send(:identifier_list, columns_for(table, :except => op[:name]))
|
|
30
|
+
defined_columns_str = column_list_sql(defined_columns_for(table, :except => op[:name]))
|
|
31
|
+
["CREATE TEMPORARY TABLE #{bt}(#{defined_columns_str})",
|
|
32
|
+
"INSERT INTO #{bt} SELECT #{columns_str} FROM #{qt}",
|
|
33
|
+
"DROP TABLE #{qt}",
|
|
34
|
+
"CREATE TABLE #{qt}(#{defined_columns_str})",
|
|
35
|
+
"INSERT INTO #{qt} SELECT #{columns_str} FROM #{bt}",
|
|
36
|
+
"DROP TABLE #{bt}"]
|
|
37
|
+
when :rename_column
|
|
38
|
+
qt = quote_schema_table(table)
|
|
39
|
+
bt = quote_identifier(backup_table_name(qt.gsub('`', '')))
|
|
40
|
+
old_columns = dataset.send(:identifier_list, columns_for(table))
|
|
41
|
+
new_columns_arr = columns_for(table)
|
|
42
|
+
|
|
43
|
+
# Replace the old column in place. This is extremely important.
|
|
44
|
+
new_columns_arr[new_columns_arr.index(op[:name])] = op[:new_name]
|
|
45
|
+
|
|
46
|
+
new_columns = dataset.send(:identifier_list, new_columns_arr)
|
|
47
|
+
|
|
48
|
+
def_old_columns = column_list_sql(defined_columns_for(table))
|
|
49
|
+
|
|
50
|
+
def_new_columns_arr = defined_columns_for(table).map do |c|
|
|
51
|
+
c[:name] = op[:new_name].to_s if c[:name] == op[:name].to_s
|
|
52
|
+
c
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def_new_columns = column_list_sql(def_new_columns_arr)
|
|
56
|
+
|
|
57
|
+
[
|
|
58
|
+
"CREATE TEMPORARY TABLE #{bt}(#{def_old_columns})",
|
|
59
|
+
"INSERT INTO #{bt}(#{old_columns}) SELECT #{old_columns} FROM #{qt}",
|
|
60
|
+
"DROP TABLE #{qt}",
|
|
61
|
+
"CREATE TABLE #{qt}(#{def_new_columns})",
|
|
62
|
+
"INSERT INTO #{qt}(#{new_columns}) SELECT #{old_columns} FROM #{bt}",
|
|
63
|
+
"DROP TABLE #{bt}"
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
else
|
|
67
|
+
raise Error, "Unsupported ALTER TABLE operation"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A symbol signifying the value of the auto_vacuum PRAGMA.
|
|
72
|
+
def auto_vacuum
|
|
73
|
+
AUTO_VACUUM[pragma_get(:auto_vacuum).to_i]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
|
|
77
|
+
# :incremental).
|
|
78
|
+
def auto_vacuum=(value)
|
|
79
|
+
value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
|
80
|
+
pragma_set(:auto_vacuum, value)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get the value of the given PRAGMA.
|
|
84
|
+
def pragma_get(name)
|
|
85
|
+
self["PRAGMA #{name}"].single_value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Set the value of the given PRAGMA to value.
|
|
89
|
+
def pragma_set(name, value)
|
|
90
|
+
execute_ddl("PRAGMA #{name} = #{value}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# A symbol signifying the value of the synchronous PRAGMA.
|
|
94
|
+
def synchronous
|
|
95
|
+
SYNCHRONOUS[pragma_get(:synchronous).to_i]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full).
|
|
99
|
+
def synchronous=(value)
|
|
100
|
+
value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
|
101
|
+
pragma_set(:synchronous, value)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Array of symbols specifying the table names in the current database.
|
|
105
|
+
#
|
|
106
|
+
# Options:
|
|
107
|
+
# * :server - Set the server to use.
|
|
108
|
+
def tables(opts={})
|
|
109
|
+
ds = self[:sqlite_master].server(opts[:server]).filter(TABLES_FILTER)
|
|
110
|
+
ds.identifier_output_method = nil
|
|
111
|
+
ds.identifier_input_method = nil
|
|
112
|
+
ds2 = dataset
|
|
113
|
+
ds.map{|r| ds2.send(:output_identifier, r[:name])}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A symbol signifying the value of the temp_store PRAGMA.
|
|
117
|
+
def temp_store
|
|
118
|
+
TEMP_STORE[pragma_get(:temp_store).to_i]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory).
|
|
122
|
+
def temp_store=(value)
|
|
123
|
+
value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
|
124
|
+
pragma_set(:temp_store, value)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# The array of column symbols in the table, except for ones given in opts[:except]
|
|
130
|
+
def backup_table_name(table, opts={})
|
|
131
|
+
(opts[:times]||1000).times do |i|
|
|
132
|
+
table_name = "#{table}_backup#{i}"
|
|
133
|
+
return table_name unless table_exists?(table_name)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The array of column symbols in the table, except for ones given in opts[:except]
|
|
138
|
+
def columns_for(table, opts={})
|
|
139
|
+
cols = schema_parse_table(table, {}).map{|c| c[0]}
|
|
140
|
+
cols = cols - Array(opts[:except])
|
|
141
|
+
cols
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The array of column schema hashes, except for the ones given in opts[:except]
|
|
145
|
+
def defined_columns_for(table, opts={})
|
|
146
|
+
cols = parse_pragma(table, {})
|
|
147
|
+
cols.each{|c| c[:default] = LiteralString.new(c[:default]) if c[:default]}
|
|
148
|
+
if opts[:except]
|
|
149
|
+
nono= Array(opts[:except]).compact.map{|n| n.to_s}
|
|
150
|
+
cols.reject!{|c| nono.include? c[:name] }
|
|
151
|
+
end
|
|
152
|
+
cols
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
|
156
|
+
def identifier_input_method_default
|
|
157
|
+
nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
|
|
161
|
+
def identifier_output_method_default
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Parse the output of the table_info pragma
|
|
166
|
+
def parse_pragma(table_name, opts)
|
|
167
|
+
ds2 = dataset
|
|
168
|
+
ds = self["PRAGMA table_info(?)", ds2.send(:input_identifier, table_name)]
|
|
169
|
+
ds.identifier_output_method = nil
|
|
170
|
+
ds.map do |row|
|
|
171
|
+
row.delete(:cid)
|
|
172
|
+
row[:allow_null] = row.delete(:notnull).to_i == 0
|
|
173
|
+
row[:default] = row.delete(:dflt_value)
|
|
174
|
+
row[:primary_key] = row.delete(:pk).to_i == 1
|
|
175
|
+
row[:default] = nil if blank_object?(row[:default])
|
|
176
|
+
row[:db_type] = row.delete(:type)
|
|
177
|
+
row[:type] = schema_column_type(row[:db_type])
|
|
178
|
+
row
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# SQLite supports schema parsing using the table_info PRAGMA, so
|
|
183
|
+
# parse the output of that into the format Sequel expects.
|
|
184
|
+
def schema_parse_table(table_name, opts)
|
|
185
|
+
ds = dataset
|
|
186
|
+
parse_pragma(table_name, opts).map do |row|
|
|
187
|
+
[ds.send(:output_identifier, row.delete(:name)), row]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Override the standard type conversions with SQLite specific ones
|
|
192
|
+
def type_literal_base(column)
|
|
193
|
+
TYPES[column[:type]]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Instance methods for datasets that connect to an SQLite database
|
|
198
|
+
module DatasetMethods
|
|
199
|
+
include Dataset::UnsupportedIntersectExceptAll
|
|
200
|
+
include Dataset::UnsupportedIsTrue
|
|
201
|
+
|
|
202
|
+
# SQLite does not support pattern matching via regular expressions.
|
|
203
|
+
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
|
204
|
+
# ILIKE.
|
|
205
|
+
def complex_expression_sql(op, args)
|
|
206
|
+
case op
|
|
207
|
+
when :~, :'!~', :'~*', :'!~*'
|
|
208
|
+
raise Error, "SQLite does not support pattern matching via regular expressions"
|
|
209
|
+
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
210
|
+
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
|
211
|
+
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
|
212
|
+
else
|
|
213
|
+
super(op, args)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
|
218
|
+
# Since we want to always return the count of records, add a condition
|
|
219
|
+
# that is always true and then delete.
|
|
220
|
+
def delete(opts = (defarg=true;{}))
|
|
221
|
+
# check if no filter is specified
|
|
222
|
+
if defarg
|
|
223
|
+
@opts[:where] ? super() : filter(1=>1).delete
|
|
224
|
+
else
|
|
225
|
+
opts = @opts.merge(opts)
|
|
226
|
+
super(opts[:where] ? opts : opts.merge(:where=>{1=>1}))
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Insert the values into the database.
|
|
231
|
+
def insert(*values)
|
|
232
|
+
execute_insert(insert_sql(*values))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Allow inserting of values directly from a dataset.
|
|
236
|
+
def insert_sql(*values)
|
|
237
|
+
if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
|
|
238
|
+
"INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
|
|
239
|
+
else
|
|
240
|
+
super(*values)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
|
245
|
+
def quoted_identifier(c)
|
|
246
|
+
"`#{c}`"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
def literal_blob(v)
|
|
252
|
+
blob = ''
|
|
253
|
+
v.each_byte{|x| blob << sprintf('%02x', x)}
|
|
254
|
+
"X'#{blob}'"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# SQLite uses string literals instead of identifiers in AS clauses.
|
|
258
|
+
def as_sql(expression, aliaz)
|
|
259
|
+
"#{expression} AS #{literal(aliaz.to_s)}"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
require 'sqlite3'
|
|
2
|
+
Sequel.require 'adapters/shared/sqlite'
|
|
3
|
+
|
|
4
|
+
module Sequel
|
|
5
|
+
# Top level module for holding all SQLite-related modules and classes
|
|
6
|
+
# for Sequel.
|
|
7
|
+
module SQLite
|
|
8
|
+
# Database class for PostgreSQL databases used with Sequel and the
|
|
9
|
+
# ruby-sqlite3 driver.
|
|
10
|
+
class Database < Sequel::Database
|
|
11
|
+
UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
|
|
12
|
+
include ::Sequel::SQLite::DatabaseMethods
|
|
13
|
+
|
|
14
|
+
set_adapter_scheme :sqlite
|
|
15
|
+
|
|
16
|
+
# Mimic the file:// uri, by having 2 preceding slashes specify a relative
|
|
17
|
+
# path, and 3 preceding slashes specify an absolute path.
|
|
18
|
+
def self.uri_to_options(uri) # :nodoc:
|
|
19
|
+
{ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private_class_method :uri_to_options
|
|
23
|
+
|
|
24
|
+
# Connect to the database. Since SQLite is a file based database,
|
|
25
|
+
# the only options available are :database (to specify the database
|
|
26
|
+
# name), and :timeout, to specify how long to wait for the database to
|
|
27
|
+
# be available if it is locked, given in milliseconds (default is 5000).
|
|
28
|
+
def connect(server)
|
|
29
|
+
opts = server_opts(server)
|
|
30
|
+
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
|
31
|
+
db = ::SQLite3::Database.new(opts[:database])
|
|
32
|
+
db.busy_timeout(opts.fetch(:timeout, 5000))
|
|
33
|
+
db.type_translation = true
|
|
34
|
+
|
|
35
|
+
# Handle datetime's with Sequel.datetime_class
|
|
36
|
+
prok = proc do |t,v|
|
|
37
|
+
v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
|
|
38
|
+
Sequel.string_to_datetime(v)
|
|
39
|
+
end
|
|
40
|
+
db.translator.add_translator("timestamp", &prok)
|
|
41
|
+
db.translator.add_translator("datetime", &prok)
|
|
42
|
+
|
|
43
|
+
# Handle numeric values with BigDecimal
|
|
44
|
+
prok = proc{|t,v| BigDecimal.new(v) rescue v}
|
|
45
|
+
db.translator.add_translator("numeric", &prok)
|
|
46
|
+
db.translator.add_translator("decimal", &prok)
|
|
47
|
+
db.translator.add_translator("money", &prok)
|
|
48
|
+
|
|
49
|
+
# Handle floating point values with Float
|
|
50
|
+
prok = proc{|t,v| Float(v) rescue v}
|
|
51
|
+
db.translator.add_translator("float", &prok)
|
|
52
|
+
db.translator.add_translator("real", &prok)
|
|
53
|
+
db.translator.add_translator("double precision", &prok)
|
|
54
|
+
|
|
55
|
+
# Handle blob values with Sequel::SQL::Blob
|
|
56
|
+
db.translator.add_translator("blob"){|t,v| ::Sequel::SQL::Blob.new(v)}
|
|
57
|
+
|
|
58
|
+
db
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Return instance of Sequel::SQLite::Dataset with the given options.
|
|
62
|
+
def dataset(opts = nil)
|
|
63
|
+
SQLite::Dataset.new(self, opts)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Run the given SQL with the given arguments and return the number of changed rows.
|
|
67
|
+
def execute_dui(sql, opts={})
|
|
68
|
+
_execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Run the given SQL with the given arguments and return the last inserted row id.
|
|
72
|
+
def execute_insert(sql, opts={})
|
|
73
|
+
_execute(sql, opts){|conn| conn.execute(sql, opts[:arguments]); conn.last_insert_row_id}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Run the given SQL with the given arguments and yield each row.
|
|
77
|
+
def execute(sql, opts={}, &block)
|
|
78
|
+
_execute(sql, opts){|conn| conn.query(sql, opts[:arguments], &block)}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Run the given SQL with the given arguments and return the first value of the first row.
|
|
82
|
+
def single_value(sql, opts={})
|
|
83
|
+
_execute(sql, opts){|conn| conn.get_first_value(sql, opts[:arguments])}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Use the native driver transaction method if there isn't already a transaction
|
|
87
|
+
# in progress on the connection, always yielding a connection inside a transaction
|
|
88
|
+
# transaction.
|
|
89
|
+
def transaction(opts={})
|
|
90
|
+
unless opts.is_a?(Hash)
|
|
91
|
+
Deprecation.deprecate('Passing an argument other than a Hash to Database#transaction', "Use DB.transaction(:server=>#{opts.inspect})")
|
|
92
|
+
opts = {:server=>opts}
|
|
93
|
+
end
|
|
94
|
+
synchronize(opts[:server]) do |conn|
|
|
95
|
+
return yield(conn) if conn.transaction_active?
|
|
96
|
+
begin
|
|
97
|
+
result = nil
|
|
98
|
+
log_info('Transaction.begin')
|
|
99
|
+
conn.transaction{result = yield(conn)}
|
|
100
|
+
result
|
|
101
|
+
rescue ::Exception => e
|
|
102
|
+
log_info('Transaction.rollback')
|
|
103
|
+
transaction_error(e, SQLite3::Exception)
|
|
104
|
+
ensure
|
|
105
|
+
log_info('Transaction.commit') unless e
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# Log the SQL and the arguments, and yield an available connection. Rescue
|
|
113
|
+
# any SQLite3::Exceptions and turn the into Error::InvalidStatements.
|
|
114
|
+
def _execute(sql, opts)
|
|
115
|
+
begin
|
|
116
|
+
log_info(sql, opts[:arguments])
|
|
117
|
+
synchronize(opts[:server]){|conn| yield conn}
|
|
118
|
+
rescue SQLite3::Exception => e
|
|
119
|
+
raise_error(e)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# SQLite does not need the pool to convert exceptions.
|
|
124
|
+
# Also, force the max connections to 1 if a memory database is being
|
|
125
|
+
# used, as otherwise each connection gets a separate database.
|
|
126
|
+
def connection_pool_default_options
|
|
127
|
+
o = super.merge(:pool_convert_exceptions=>false)
|
|
128
|
+
# Default to only a single connection if a memory database is used,
|
|
129
|
+
# because otherwise each connection will get a separate database
|
|
130
|
+
o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
|
|
131
|
+
o
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Disconnect given connections from the database.
|
|
135
|
+
def disconnect_connection(c)
|
|
136
|
+
c.close
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
|
|
141
|
+
class Dataset < Sequel::Dataset
|
|
142
|
+
include ::Sequel::SQLite::DatasetMethods
|
|
143
|
+
|
|
144
|
+
EXPLAIN = 'EXPLAIN %s'.freeze
|
|
145
|
+
PREPARED_ARG_PLACEHOLDER = ':'.freeze
|
|
146
|
+
|
|
147
|
+
# SQLite already supports named bind arguments, so use directly.
|
|
148
|
+
module ArgumentMapper
|
|
149
|
+
include Sequel::Dataset::ArgumentMapper
|
|
150
|
+
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
# Return a hash with the same values as the given hash,
|
|
154
|
+
# but with the keys converted to strings.
|
|
155
|
+
def map_to_prepared_args(hash)
|
|
156
|
+
args = {}
|
|
157
|
+
hash.each{|k,v| args[k.to_s] = v}
|
|
158
|
+
args
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
# SQLite uses a : before the name of the argument for named
|
|
164
|
+
# arguments.
|
|
165
|
+
def prepared_arg(k)
|
|
166
|
+
LiteralString.new("#{prepared_arg_placeholder}#{k}")
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# SQLite prepared statement uses a new prepared statement each time
|
|
171
|
+
# it is called, but it does use the bind arguments.
|
|
172
|
+
module PreparedStatementMethods
|
|
173
|
+
include ArgumentMapper
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Run execute_select on the database with the given SQL and the stored
|
|
178
|
+
# bind arguments.
|
|
179
|
+
def execute(sql, opts={}, &block)
|
|
180
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
184
|
+
def execute_dui(sql, opts={}, &block)
|
|
185
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
|
189
|
+
def execute_insert(sql, opts={}, &block)
|
|
190
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Prepare an unnamed statement of the given type and call it with the
|
|
195
|
+
# given values.
|
|
196
|
+
def call(type, hash, values=nil, &block)
|
|
197
|
+
prepare(type, nil, values).call(hash, &block)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Return an array of strings specifying a query explanation for the
|
|
201
|
+
# current dataset.
|
|
202
|
+
def explain
|
|
203
|
+
res = []
|
|
204
|
+
@db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
|
|
205
|
+
res
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Yield a hash for each row in the dataset.
|
|
209
|
+
def fetch_rows(sql)
|
|
210
|
+
execute(sql) do |result|
|
|
211
|
+
@columns = result.columns.map{|c| output_identifier(c)}
|
|
212
|
+
column_count = @columns.size
|
|
213
|
+
result.each do |values|
|
|
214
|
+
row = {}
|
|
215
|
+
column_count.times {|i| row[@columns[i]] = values[i]}
|
|
216
|
+
yield row
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Prepare the given type of query with the given name and store
|
|
222
|
+
# it in the database. Note that a new native prepared statement is
|
|
223
|
+
# created on each call to this prepared statement.
|
|
224
|
+
def prepare(type, name=nil, values=nil)
|
|
225
|
+
ps = to_prepared_statement(type, values)
|
|
226
|
+
ps.extend(PreparedStatementMethods)
|
|
227
|
+
db.prepared_statements[name] = ps if name
|
|
228
|
+
ps
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
def literal_string(v)
|
|
234
|
+
"'#{::SQLite3::Database.quote(v)}'"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# SQLite uses a : before the name of the argument as a placeholder.
|
|
238
|
+
def prepared_arg_placeholder
|
|
239
|
+
PREPARED_ARG_PLACEHOLDER
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|