viking-sequel 3.10.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 +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -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/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +346 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Progress
|
3
|
+
module DatabaseMethods
|
4
|
+
|
5
|
+
# Progress uses the :progress database type.
|
6
|
+
def database_type
|
7
|
+
:progress
|
8
|
+
end
|
9
|
+
|
10
|
+
def dataset(opts = nil)
|
11
|
+
ds = super
|
12
|
+
ds.extend(DatasetMethods)
|
13
|
+
ds
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module DatasetMethods
|
18
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
|
19
|
+
|
20
|
+
# Progress requires SQL standard datetimes
|
21
|
+
def requires_sql_standard_datetimes?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# Progress does not support INTERSECT or EXCEPT
|
26
|
+
def supports_intersect_except?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def select_clause_methods
|
33
|
+
SELECT_CLAUSE_METHODS
|
34
|
+
end
|
35
|
+
|
36
|
+
# Progress uses TOP for limit, but it is only supported in Progress 10.
|
37
|
+
# The Progress adapter targets Progress 9, so it silently ignores the option.
|
38
|
+
def select_limit_sql(sql)
|
39
|
+
raise(Error, "OFFSET not supported") if @opts[:offset]
|
40
|
+
#sql << " TOP #{@opts[:limit]}" if @opts[:limit]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,389 @@
|
|
1
|
+
module Sequel
|
2
|
+
module SQLite
|
3
|
+
# No matter how you connect to SQLite, the following Database options
|
4
|
+
# can be used to set PRAGMAs on connections in a thread-safe manner:
|
5
|
+
# :auto_vacuum, :foreign_keys, :synchronous, and :temp_store.
|
6
|
+
module DatabaseMethods
|
7
|
+
AUTO_VACUUM = [:none, :full, :incremental].freeze
|
8
|
+
PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
|
9
|
+
SYNCHRONOUS = [:off, :normal, :full].freeze
|
10
|
+
TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
|
11
|
+
TEMP_STORE = [:default, :file, :memory].freeze
|
12
|
+
|
13
|
+
# Run all alter_table commands in a transaction. This is technically only
|
14
|
+
# needed for drop column.
|
15
|
+
def alter_table(name, generator=nil, &block)
|
16
|
+
remove_cached_schema(name)
|
17
|
+
generator ||= Schema::AlterTableGenerator.new(self, &block)
|
18
|
+
transaction{generator.operations.each{|op| alter_table_sql_list(name, [op]).flatten.each{|sql| execute_ddl(sql)}}}
|
19
|
+
end
|
20
|
+
|
21
|
+
# A symbol signifying the value of the auto_vacuum PRAGMA.
|
22
|
+
def auto_vacuum
|
23
|
+
AUTO_VACUUM[pragma_get(:auto_vacuum).to_i]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
|
27
|
+
# :incremental). See pragma_set. Consider using the :auto_vacuum
|
28
|
+
# Database option instead.
|
29
|
+
def auto_vacuum=(value)
|
30
|
+
value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
|
31
|
+
pragma_set(:auto_vacuum, value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# SQLite uses the :sqlite database type.
|
35
|
+
def database_type
|
36
|
+
:sqlite
|
37
|
+
end
|
38
|
+
|
39
|
+
# Boolean signifying the value of the foreign_keys PRAGMA, or nil
|
40
|
+
# if not using SQLite 3.6.19+.
|
41
|
+
def foreign_keys
|
42
|
+
pragma_get(:foreign_keys).to_i == 1 if sqlite_version >= 30619
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set the foreign_keys PRAGMA using the given boolean value, if using
|
46
|
+
# SQLite 3.6.19+. If not using 3.6.19+, no error is raised. See pragma_set.
|
47
|
+
# Consider using the :foreign_keys Database option instead.
|
48
|
+
def foreign_keys=(value)
|
49
|
+
pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a hash containing index information. Hash keys are index name symbols.
|
53
|
+
# Values are subhashes with two keys, :columns and :unique. The value of :columns
|
54
|
+
# is an array of symbols of column names. The value of :unique is true or false
|
55
|
+
# depending on if the index is unique.
|
56
|
+
def indexes(table)
|
57
|
+
m = output_identifier_meth
|
58
|
+
im = input_identifier_meth
|
59
|
+
indexes = {}
|
60
|
+
begin
|
61
|
+
metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
|
62
|
+
next if r[:name] =~ PRIMARY_KEY_INDEX_RE
|
63
|
+
indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
|
64
|
+
end
|
65
|
+
rescue Sequel::DatabaseError
|
66
|
+
nil
|
67
|
+
else
|
68
|
+
indexes.each do |k, v|
|
69
|
+
v[:columns] = metadata_dataset.with_sql("PRAGMA index_info(?)", im.call(k)).map(:name).map{|x| m.call(x)}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
indexes
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get the value of the given PRAGMA.
|
76
|
+
def pragma_get(name)
|
77
|
+
self["PRAGMA #{name}"].single_value
|
78
|
+
end
|
79
|
+
|
80
|
+
# Set the value of the given PRAGMA to value.
|
81
|
+
#
|
82
|
+
# This method is not thread safe, and will not work correctly if there
|
83
|
+
# are multiple connections in the Database's connection pool. PRAGMA
|
84
|
+
# modifications should be done when the connection is created, using
|
85
|
+
# an option provided when creating the Database object.
|
86
|
+
def pragma_set(name, value)
|
87
|
+
execute_ddl("PRAGMA #{name} = #{value}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# The version of the server as an integer, where 3.6.19 = 30619.
|
91
|
+
# If the server version can't be determined, 0 is used.
|
92
|
+
def sqlite_version
|
93
|
+
return @sqlite_version if defined?(@sqlite_version)
|
94
|
+
@sqlite_version = begin
|
95
|
+
v = get{sqlite_version{}}
|
96
|
+
[10000, 100, 1].zip(v.split('.')).inject(0){|a, m| a + m[0] * Integer(m[1])}
|
97
|
+
rescue
|
98
|
+
0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# SQLite 3.6.8+ supports savepoints.
|
103
|
+
def supports_savepoints?
|
104
|
+
sqlite_version >= 30608
|
105
|
+
end
|
106
|
+
|
107
|
+
# A symbol signifying the value of the synchronous PRAGMA.
|
108
|
+
def synchronous
|
109
|
+
SYNCHRONOUS[pragma_get(:synchronous).to_i]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full). See pragma_set.
|
113
|
+
# Consider using the :synchronous Database option instead.
|
114
|
+
def synchronous=(value)
|
115
|
+
value = SYNCHRONOUS.index(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
|
116
|
+
pragma_set(:synchronous, value)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Array of symbols specifying the table names in the current database.
|
120
|
+
#
|
121
|
+
# Options:
|
122
|
+
# * :server - Set the server to use.
|
123
|
+
def tables(opts={})
|
124
|
+
m = output_identifier_meth
|
125
|
+
metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(TABLES_FILTER).map{|r| m.call(r[:name])}
|
126
|
+
end
|
127
|
+
|
128
|
+
# A symbol signifying the value of the temp_store PRAGMA.
|
129
|
+
def temp_store
|
130
|
+
TEMP_STORE[pragma_get(:temp_store).to_i]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory). See pragma_set.
|
134
|
+
# Consider using the :temp_store Database option instead.
|
135
|
+
def temp_store=(value)
|
136
|
+
value = TEMP_STORE.index(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
|
137
|
+
pragma_set(:temp_store, value)
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# SQLite supports limited table modification. You can add a column
|
143
|
+
# or an index. Dropping columns is supported by copying the table into
|
144
|
+
# a temporary table, dropping the table, and creating a new table without
|
145
|
+
# the column inside of a transaction.
|
146
|
+
def alter_table_sql(table, op)
|
147
|
+
case op[:op]
|
148
|
+
when :add_index, :drop_index
|
149
|
+
super
|
150
|
+
when :add_column
|
151
|
+
if op[:unique] || op[:primary_key]
|
152
|
+
duplicate_table(table){|columns| columns.push(op)}
|
153
|
+
else
|
154
|
+
super
|
155
|
+
end
|
156
|
+
when :drop_column
|
157
|
+
ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
|
158
|
+
duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
|
159
|
+
when :rename_column
|
160
|
+
ncp = lambda{|nc| nc.map!{|c| c.to_s == op[:name].to_s ? op[:new_name] : c}}
|
161
|
+
duplicate_table(table, :new_columns_proc=>ncp){|columns| columns.each{|s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s}}
|
162
|
+
when :set_column_default
|
163
|
+
duplicate_table(table){|columns| columns.each{|s| s[:default] = op[:default] if s[:name].to_s == op[:name].to_s}}
|
164
|
+
when :set_column_null
|
165
|
+
duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
|
166
|
+
when :set_column_type
|
167
|
+
duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
|
168
|
+
else
|
169
|
+
raise Error, "Unsupported ALTER TABLE operation"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# The array of column symbols in the table, except for ones given in opts[:except]
|
174
|
+
def backup_table_name(table, opts={})
|
175
|
+
table = table.gsub('`', '')
|
176
|
+
(opts[:times]||1000).times do |i|
|
177
|
+
table_name = "#{table}_backup#{i}"
|
178
|
+
return table_name unless table_exists?(table_name)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Allow use without a generator, needed for the alter table hackery that Sequel allows.
|
183
|
+
def column_list_sql(generator)
|
184
|
+
generator.is_a?(Schema::Generator) ? super : generator.map{|c| column_definition_sql(c)}.join(', ')
|
185
|
+
end
|
186
|
+
|
187
|
+
# The array of column schema hashes, except for the ones given in opts[:except]
|
188
|
+
def defined_columns_for(table, opts={})
|
189
|
+
cols = parse_pragma(table, {})
|
190
|
+
cols.each do |c|
|
191
|
+
c[:default] = LiteralString.new(c[:default]) if c[:default]
|
192
|
+
c[:type] = c[:db_type]
|
193
|
+
end
|
194
|
+
if opts[:except]
|
195
|
+
nono= Array(opts[:except]).compact.map{|n| n.to_s}
|
196
|
+
cols.reject!{|c| nono.include? c[:name] }
|
197
|
+
end
|
198
|
+
cols
|
199
|
+
end
|
200
|
+
|
201
|
+
# Duplicate an existing table by creating a new table, copying all records
|
202
|
+
# from the existing table into the new table, deleting the existing table
|
203
|
+
# and renaming the new table to the existing table's name.
|
204
|
+
def duplicate_table(table, opts={})
|
205
|
+
remove_cached_schema(table)
|
206
|
+
def_columns = defined_columns_for(table)
|
207
|
+
old_columns = def_columns.map{|c| c[:name]}
|
208
|
+
opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
|
209
|
+
|
210
|
+
yield def_columns if block_given?
|
211
|
+
def_columns_str = column_list_sql(def_columns)
|
212
|
+
new_columns = old_columns.dup
|
213
|
+
opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
|
214
|
+
|
215
|
+
qt = quote_schema_table(table)
|
216
|
+
bt = quote_identifier(backup_table_name(qt))
|
217
|
+
[
|
218
|
+
"CREATE TABLE #{bt}(#{def_columns_str})",
|
219
|
+
"INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
|
220
|
+
"DROP TABLE #{qt}",
|
221
|
+
"ALTER TABLE #{bt} RENAME TO #{qt}"
|
222
|
+
]
|
223
|
+
end
|
224
|
+
|
225
|
+
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
|
226
|
+
def identifier_input_method_default
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
|
230
|
+
# SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
|
231
|
+
def identifier_output_method_default
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
|
235
|
+
# Array of PRAGMA SQL statements based on the Database options that should be applied to
|
236
|
+
# new connections.
|
237
|
+
def connection_pragmas
|
238
|
+
ps = []
|
239
|
+
v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
|
240
|
+
ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
|
241
|
+
[[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
|
242
|
+
if v = opts[prag]
|
243
|
+
raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
|
244
|
+
ps << "PRAGMA #{prag} = #{v}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
ps
|
248
|
+
end
|
249
|
+
|
250
|
+
# Parse the output of the table_info pragma
|
251
|
+
def parse_pragma(table_name, opts)
|
252
|
+
metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth.call(table_name)).map do |row|
|
253
|
+
row.delete(:cid)
|
254
|
+
row[:allow_null] = row.delete(:notnull).to_i == 0
|
255
|
+
row[:default] = row.delete(:dflt_value)
|
256
|
+
row[:primary_key] = row.delete(:pk).to_i == 1
|
257
|
+
row[:default] = nil if blank_object?(row[:default]) || row[:default] == 'NULL'
|
258
|
+
row[:db_type] = row.delete(:type)
|
259
|
+
row[:type] = schema_column_type(row[:db_type])
|
260
|
+
row
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# SQLite treats integer primary keys as autoincrementing (alias of rowid).
|
265
|
+
def schema_autoincrementing_primary_key?(schema)
|
266
|
+
super and schema[:db_type].downcase == 'integer'
|
267
|
+
end
|
268
|
+
|
269
|
+
# SQLite supports schema parsing using the table_info PRAGMA, so
|
270
|
+
# parse the output of that into the format Sequel expects.
|
271
|
+
def schema_parse_table(table_name, opts)
|
272
|
+
m = output_identifier_meth
|
273
|
+
parse_pragma(table_name, opts).map do |row|
|
274
|
+
[m.call(row.delete(:name)), row]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# SQLite uses the integer data type even for bignums. This is because they
|
279
|
+
# are both stored internally as text, and converted when returned from
|
280
|
+
# the database. Using an integer type instead of bigint makes it more likely
|
281
|
+
# that software will automatically return the column as an integer.
|
282
|
+
def type_literal_generic_bignum(column)
|
283
|
+
:integer
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Instance methods for datasets that connect to an SQLite database
|
288
|
+
module DatasetMethods
|
289
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
290
|
+
CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
|
291
|
+
|
292
|
+
# SQLite does not support pattern matching via regular expressions.
|
293
|
+
# SQLite is case insensitive (depending on pragma), so use LIKE for
|
294
|
+
# ILIKE.
|
295
|
+
def complex_expression_sql(op, args)
|
296
|
+
case op
|
297
|
+
when :~, :'!~', :'~*', :'!~*'
|
298
|
+
raise Error, "SQLite does not support pattern matching via regular expressions"
|
299
|
+
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
300
|
+
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
301
|
+
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
302
|
+
else
|
303
|
+
super(op, args)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
|
308
|
+
def constant_sql(constant)
|
309
|
+
CONSTANT_MAP[constant] || super
|
310
|
+
end
|
311
|
+
|
312
|
+
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
313
|
+
# Since we want to always return the count of records, add a condition
|
314
|
+
# that is always true and then delete.
|
315
|
+
def delete
|
316
|
+
@opts[:where] ? super : filter(1=>1).delete
|
317
|
+
end
|
318
|
+
|
319
|
+
# Return an array of strings specifying a query explanation for a SELECT of the
|
320
|
+
# current dataset.
|
321
|
+
def explain
|
322
|
+
db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}").
|
323
|
+
map{|x| "#{x[:addr]}|#{x[:opcode]}|#{(1..5).map{|i| x[:"p#{i}"]}.join('|')}|#{x[:comment]}"}
|
324
|
+
end
|
325
|
+
|
326
|
+
# HAVING requires GROUP BY on SQLite
|
327
|
+
def having(*cond, &block)
|
328
|
+
raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
|
329
|
+
super
|
330
|
+
end
|
331
|
+
|
332
|
+
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
333
|
+
def quoted_identifier(c)
|
334
|
+
"`#{c}`"
|
335
|
+
end
|
336
|
+
|
337
|
+
# SQLite does not support INTERSECT ALL or EXCEPT ALL
|
338
|
+
def supports_intersect_except_all?
|
339
|
+
false
|
340
|
+
end
|
341
|
+
|
342
|
+
# SQLite does not support IS TRUE
|
343
|
+
def supports_is_true?
|
344
|
+
false
|
345
|
+
end
|
346
|
+
|
347
|
+
# SQLite does not support multiple columns for the IN/NOT IN operators
|
348
|
+
def supports_multiple_column_in?
|
349
|
+
false
|
350
|
+
end
|
351
|
+
|
352
|
+
# SQLite supports timezones in literal timestamps, since it stores them
|
353
|
+
# as text.
|
354
|
+
def supports_timestamp_timezones?
|
355
|
+
true
|
356
|
+
end
|
357
|
+
|
358
|
+
private
|
359
|
+
|
360
|
+
# SQLite uses string literals instead of identifiers in AS clauses.
|
361
|
+
def as_sql(expression, aliaz)
|
362
|
+
aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
|
363
|
+
"#{expression} AS #{literal(aliaz.to_s)}"
|
364
|
+
end
|
365
|
+
|
366
|
+
# SQLite uses a preceding X for hex escaping strings
|
367
|
+
def literal_blob(v)
|
368
|
+
blob = ''
|
369
|
+
v.each_byte{|x| blob << sprintf('%02x', x)}
|
370
|
+
"X'#{blob}'"
|
371
|
+
end
|
372
|
+
|
373
|
+
# SQLite does not support the SQL WITH clause
|
374
|
+
def select_clause_methods
|
375
|
+
SELECT_CLAUSE_METHODS
|
376
|
+
end
|
377
|
+
|
378
|
+
# Support FOR SHARE locking when using the :share lock style.
|
379
|
+
def select_lock_sql(sql)
|
380
|
+
super unless @opts[:lock] == :update
|
381
|
+
end
|
382
|
+
|
383
|
+
# SQLite treats a DELETE with no WHERE clause as a TRUNCATE
|
384
|
+
def _truncate_sql(table)
|
385
|
+
"DELETE FROM #{table}"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
begin
|
3
|
+
SQLite3::Database.instance_method(:type_translation)
|
4
|
+
rescue
|
5
|
+
raise(Sequel::Error, "SQLite3::Database#type_translation is not defined. If you are using the sqlite3 gem, please install the sqlite3-ruby gem.")
|
6
|
+
end
|
7
|
+
Sequel.require 'adapters/shared/sqlite'
|
8
|
+
|
9
|
+
module Sequel
|
10
|
+
# Top level module for holding all SQLite-related modules and classes
|
11
|
+
# for Sequel.
|
12
|
+
module SQLite
|
13
|
+
# Database class for SQLite databases used with Sequel and the
|
14
|
+
# ruby-sqlite3 driver.
|
15
|
+
class Database < Sequel::Database
|
16
|
+
UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
|
17
|
+
include ::Sequel::SQLite::DatabaseMethods
|
18
|
+
|
19
|
+
set_adapter_scheme :sqlite
|
20
|
+
|
21
|
+
# Mimic the file:// uri, by having 2 preceding slashes specify a relative
|
22
|
+
# path, and 3 preceding slashes specify an absolute path.
|
23
|
+
def self.uri_to_options(uri) # :nodoc:
|
24
|
+
{ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
|
25
|
+
end
|
26
|
+
|
27
|
+
private_class_method :uri_to_options
|
28
|
+
|
29
|
+
# Connect to the database. Since SQLite is a file based database,
|
30
|
+
# the only options available are :database (to specify the database
|
31
|
+
# name), and :timeout, to specify how long to wait for the database to
|
32
|
+
# be available if it is locked, given in milliseconds (default is 5000).
|
33
|
+
def connect(server)
|
34
|
+
opts = server_opts(server)
|
35
|
+
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
36
|
+
db = ::SQLite3::Database.new(opts[:database])
|
37
|
+
db.busy_timeout(opts.fetch(:timeout, 5000))
|
38
|
+
db.type_translation = true
|
39
|
+
|
40
|
+
connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
|
41
|
+
|
42
|
+
# Handle datetimes with Sequel.datetime_class
|
43
|
+
prok = proc do |t,v|
|
44
|
+
v = Time.at(v.to_i).iso8601 if UNIX_EPOCH_TIME_FORMAT.match(v)
|
45
|
+
Sequel.database_to_application_timestamp(v)
|
46
|
+
end
|
47
|
+
db.translator.add_translator("timestamp", &prok)
|
48
|
+
db.translator.add_translator("datetime", &prok)
|
49
|
+
|
50
|
+
# Handle numeric values with BigDecimal
|
51
|
+
prok = proc{|t,v| BigDecimal.new(v) rescue v}
|
52
|
+
db.translator.add_translator("numeric", &prok)
|
53
|
+
db.translator.add_translator("decimal", &prok)
|
54
|
+
db.translator.add_translator("money", &prok)
|
55
|
+
|
56
|
+
# Handle floating point values with Float
|
57
|
+
prok = proc{|t,v| Float(v) rescue v}
|
58
|
+
db.translator.add_translator("float", &prok)
|
59
|
+
db.translator.add_translator("real", &prok)
|
60
|
+
db.translator.add_translator("double precision", &prok)
|
61
|
+
|
62
|
+
# Handle blob values with Sequel::SQL::Blob
|
63
|
+
db.translator.add_translator("blob"){|t,v| ::Sequel::SQL::Blob.new(v)}
|
64
|
+
|
65
|
+
db
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return instance of Sequel::SQLite::Dataset with the given options.
|
69
|
+
def dataset(opts = nil)
|
70
|
+
SQLite::Dataset.new(self, opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Run the given SQL with the given arguments and return the number of changed rows.
|
74
|
+
def execute_dui(sql, opts={})
|
75
|
+
_execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute_batch(sql, opts[:arguments])}; conn.changes}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Run the given SQL with the given arguments and return the last inserted row id.
|
79
|
+
def execute_insert(sql, opts={})
|
80
|
+
_execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.execute(sql, opts[:arguments])}; conn.last_insert_row_id}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Run the given SQL with the given arguments and yield each row.
|
84
|
+
def execute(sql, opts={})
|
85
|
+
_execute(opts) do |conn|
|
86
|
+
begin
|
87
|
+
yield(result = log_yield(sql, opts[:arguments]){conn.query(sql, opts[:arguments])})
|
88
|
+
ensure
|
89
|
+
result.close if result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Run the given SQL with the given arguments and return the first value of the first row.
|
95
|
+
def single_value(sql, opts={})
|
96
|
+
_execute(opts){|conn| log_yield(sql, opts[:arguments]){conn.get_first_value(sql, opts[:arguments])}}
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Yield an available connection. Rescue
|
102
|
+
# any SQLite3::Exceptions and turn them into DatabaseErrors.
|
103
|
+
def _execute(opts, &block)
|
104
|
+
begin
|
105
|
+
synchronize(opts[:server], &block)
|
106
|
+
rescue SQLite3::Exception => e
|
107
|
+
raise_error(e)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# The SQLite adapter does not need the pool to convert exceptions.
|
112
|
+
# Also, force the max connections to 1 if a memory database is being
|
113
|
+
# used, as otherwise each connection gets a separate database.
|
114
|
+
def connection_pool_default_options
|
115
|
+
o = super.dup
|
116
|
+
# Default to only a single connection if a memory database is used,
|
117
|
+
# because otherwise each connection will get a separate database
|
118
|
+
o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
|
119
|
+
o
|
120
|
+
end
|
121
|
+
|
122
|
+
# The main error class that SQLite3 raises
|
123
|
+
def database_error_classes
|
124
|
+
[SQLite3::Exception]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Disconnect given connections from the database.
|
128
|
+
def disconnect_connection(c)
|
129
|
+
c.close
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
|
134
|
+
class Dataset < Sequel::Dataset
|
135
|
+
include ::Sequel::SQLite::DatasetMethods
|
136
|
+
|
137
|
+
PREPARED_ARG_PLACEHOLDER = ':'.freeze
|
138
|
+
|
139
|
+
# SQLite already supports named bind arguments, so use directly.
|
140
|
+
module ArgumentMapper
|
141
|
+
include Sequel::Dataset::ArgumentMapper
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
# Return a hash with the same values as the given hash,
|
146
|
+
# but with the keys converted to strings.
|
147
|
+
def map_to_prepared_args(hash)
|
148
|
+
args = {}
|
149
|
+
hash.each{|k,v| args[k.to_s] = v}
|
150
|
+
args
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
# SQLite uses a : before the name of the argument for named
|
156
|
+
# arguments.
|
157
|
+
def prepared_arg(k)
|
158
|
+
LiteralString.new("#{prepared_arg_placeholder}#{k}")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# SQLite prepared statement uses a new prepared statement each time
|
163
|
+
# it is called, but it does use the bind arguments.
|
164
|
+
module PreparedStatementMethods
|
165
|
+
include ArgumentMapper
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# Run execute_select on the database with the given SQL and the stored
|
170
|
+
# bind arguments.
|
171
|
+
def execute(sql, opts={}, &block)
|
172
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
176
|
+
def execute_dui(sql, opts={}, &block)
|
177
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
181
|
+
def execute_insert(sql, opts={}, &block)
|
182
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Yield a hash for each row in the dataset.
|
187
|
+
def fetch_rows(sql)
|
188
|
+
execute(sql) do |result|
|
189
|
+
i = -1
|
190
|
+
cols = result.columns.map{|c| [output_identifier(c), i+=1]}
|
191
|
+
@columns = cols.map{|c| c.first}
|
192
|
+
result.each do |values|
|
193
|
+
row = {}
|
194
|
+
cols.each{|n,i| row[n] = values[i]}
|
195
|
+
yield row
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Prepare the given type of query with the given name and store
|
201
|
+
# it in the database. Note that a new native prepared statement is
|
202
|
+
# created on each call to this prepared statement.
|
203
|
+
def prepare(type, name=nil, *values)
|
204
|
+
ps = to_prepared_statement(type, values)
|
205
|
+
ps.extend(PreparedStatementMethods)
|
206
|
+
db.prepared_statements[name] = ps if name
|
207
|
+
ps.prepared_sql
|
208
|
+
ps
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Quote the string using the adapter class method.
|
214
|
+
def literal_string(v)
|
215
|
+
"'#{::SQLite3::Database.quote(v)}'"
|
216
|
+
end
|
217
|
+
|
218
|
+
# SQLite uses a : before the name of the argument as a placeholder.
|
219
|
+
def prepared_arg_placeholder
|
220
|
+
PREPARED_ARG_PLACEHOLDER
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|