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,61 @@
|
|
1
|
+
# Allows the use of named timezones via TZInfo (requires tzinfo).
|
2
|
+
# Forces the use of DateTime as Sequel's datetime_class, since
|
3
|
+
# ruby's Time class doesn't support timezones other than local
|
4
|
+
# and UTC.
|
5
|
+
#
|
6
|
+
# This allows you to either pass strings or TZInfo::Timezone
|
7
|
+
# instance to Sequel.database_timezone=, application_timezone=, and
|
8
|
+
# typecast_timezone=. If a string is passed, it is converted to a
|
9
|
+
# TZInfo::Timezone using TZInfo::Timezone.get.
|
10
|
+
#
|
11
|
+
# Let's say you have the database server in New York and the
|
12
|
+
# application server in Los Angeles. For historical reasons, data
|
13
|
+
# is stored in local New York time, but the application server only
|
14
|
+
# services clients in Los Angeles, so you want to use New York
|
15
|
+
# time in the database and Los Angeles time in the application. This
|
16
|
+
# is easily done via:
|
17
|
+
#
|
18
|
+
# Sequel.database_timezone = 'America/New_York'
|
19
|
+
# Sequel.application_timezone = 'America/Los_Angeles'
|
20
|
+
#
|
21
|
+
# Then, before data is stored in the database, it is converted to New
|
22
|
+
# York time. When data is retrieved from the database, it is
|
23
|
+
# converted to Los Angeles time.
|
24
|
+
|
25
|
+
require 'tzinfo'
|
26
|
+
|
27
|
+
module Sequel
|
28
|
+
self.datetime_class = DateTime
|
29
|
+
|
30
|
+
module NamedTimezones
|
31
|
+
private
|
32
|
+
|
33
|
+
# Assume the given DateTime has a correct time but a wrong timezone. It is
|
34
|
+
# currently in UTC timezone, but it should be converted to the input_timzone.
|
35
|
+
# Keep the time the same but convert the timezone to the input_timezone.
|
36
|
+
# Expects the input_timezone to be a TZInfo::Timezone instance.
|
37
|
+
def convert_input_datetime_other(v, input_timezone)
|
38
|
+
local_offset = input_timezone.period_for_local(v).utc_total_offset_rational
|
39
|
+
(v - local_offset).new_offset(local_offset)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert the given DateTime to use the given output_timezone.
|
43
|
+
# Expects the output_timezone to be a TZInfo::Timezone instance.
|
44
|
+
def convert_output_datetime_other(v, output_timezone)
|
45
|
+
# TZInfo converts times, but expects the given DateTime to have an offset
|
46
|
+
# of 0 and always leaves the timezone offset as 0
|
47
|
+
v = output_timezone.utc_to_local(v.new_offset(0))
|
48
|
+
local_offset = output_timezone.period_for_local(v).utc_total_offset_rational
|
49
|
+
# Convert timezone offset from UTC to the offset for the output_timezone
|
50
|
+
(v - local_offset).new_offset(local_offset)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convert the timezone setter argument. Returns argument given by default,
|
54
|
+
# exists for easier overriding in extensions.
|
55
|
+
def convert_timezone_setter_arg(tz)
|
56
|
+
tz.is_a?(String) ? TZInfo::Timezone.get(tz) : super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
extend NamedTimezones
|
61
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# The pagination extension adds the Sequel::Dataset#paginate and #each_page methods,
|
2
|
+
# which return paginated (limited and offset) datasets with some helpful methods
|
3
|
+
# that make creating a paginated display easier.
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
class Dataset
|
7
|
+
# Returns a paginated dataset. The returned dataset is limited to
|
8
|
+
# the page size at the correct offset, and extended with the Pagination
|
9
|
+
# module. If a record count is not provided, does a count of total
|
10
|
+
# number of records for this dataset.
|
11
|
+
def paginate(page_no, page_size, record_count=nil)
|
12
|
+
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
|
13
|
+
paginated = limit(page_size, (page_no - 1) * page_size)
|
14
|
+
paginated.extend(Pagination)
|
15
|
+
paginated.set_pagination_info(page_no, page_size, record_count || count)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Yields a paginated dataset for each page and returns the receiver. Does
|
19
|
+
# a count to find the total number of records for this dataset.
|
20
|
+
def each_page(page_size, &block)
|
21
|
+
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
|
22
|
+
record_count = count
|
23
|
+
total_pages = (record_count / page_size.to_f).ceil
|
24
|
+
(1..total_pages).each{|page_no| yield paginate(page_no, page_size, record_count)}
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Holds methods that only relate to paginated datasets. Paginated dataset
|
29
|
+
# have pages starting at 1 (page 1 is offset 0, page 1 is offset page_size).
|
30
|
+
module Pagination
|
31
|
+
# The number of records per page (the final page may have fewer than
|
32
|
+
# this number of records).
|
33
|
+
attr_accessor :page_size
|
34
|
+
|
35
|
+
# The number of pages in the dataset before pagination, of which
|
36
|
+
# this paginated dataset is one.
|
37
|
+
attr_accessor :page_count
|
38
|
+
|
39
|
+
# The current page of the dataset, starting at 1 and not 0.
|
40
|
+
attr_accessor :current_page
|
41
|
+
|
42
|
+
# The total number of records in the dataset before pagination.
|
43
|
+
attr_accessor :pagination_record_count
|
44
|
+
|
45
|
+
# Returns the record range for the current page
|
46
|
+
def current_page_record_range
|
47
|
+
return (0..0) if @current_page > @page_count
|
48
|
+
|
49
|
+
a = 1 + (@current_page - 1) * @page_size
|
50
|
+
b = a + @page_size - 1
|
51
|
+
b = @pagination_record_count if b > @pagination_record_count
|
52
|
+
a..b
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the number of records in the current page
|
56
|
+
def current_page_record_count
|
57
|
+
return 0 if @current_page > @page_count
|
58
|
+
|
59
|
+
a = 1 + (@current_page - 1) * @page_size
|
60
|
+
b = a + @page_size - 1
|
61
|
+
b = @pagination_record_count if b > @pagination_record_count
|
62
|
+
b - a + 1
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns true if the current page is the first page
|
66
|
+
def first_page?
|
67
|
+
@current_page == 1
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns true if the current page is the last page
|
71
|
+
def last_page?
|
72
|
+
@current_page == @page_count
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the next page number or nil if the current page is the last page
|
76
|
+
def next_page
|
77
|
+
current_page < page_count ? (current_page + 1) : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the page range
|
81
|
+
def page_range
|
82
|
+
1..page_count
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the previous page number or nil if the current page is the first
|
86
|
+
def prev_page
|
87
|
+
current_page > 1 ? (current_page - 1) : nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sets the pagination info for this paginated dataset, and returns self.
|
91
|
+
def set_pagination_info(page_no, page_size, record_count)
|
92
|
+
@current_page = page_no
|
93
|
+
@page_size = page_size
|
94
|
+
@pagination_record_count = record_count
|
95
|
+
@page_count = (record_count / page_size.to_f).ceil
|
96
|
+
self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# The pretty_table extension adds Sequel::Dataset#print and the
|
2
|
+
# Sequel::PrettyTable class for creating nice-looking plain-text
|
3
|
+
# tables.
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
class Dataset
|
7
|
+
# Pretty prints the records in the dataset as plain-text table.
|
8
|
+
def print(*cols)
|
9
|
+
Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module PrettyTable
|
14
|
+
# Prints nice-looking plain-text tables via puts
|
15
|
+
#
|
16
|
+
# +--+-------+
|
17
|
+
# |id|name |
|
18
|
+
# |--+-------|
|
19
|
+
# |1 |fasdfas|
|
20
|
+
# |2 |test |
|
21
|
+
# +--+-------+
|
22
|
+
def self.print(records, columns = nil) # records is an array of hashes
|
23
|
+
columns ||= records.first.keys.sort_by{|x|x.to_s}
|
24
|
+
sizes = column_sizes(records, columns)
|
25
|
+
sep_line = separator_line(columns, sizes)
|
26
|
+
|
27
|
+
puts sep_line
|
28
|
+
puts header_line(columns, sizes)
|
29
|
+
puts sep_line
|
30
|
+
records.each {|r| puts data_line(columns, sizes, r)}
|
31
|
+
puts sep_line
|
32
|
+
end
|
33
|
+
|
34
|
+
### Private Module Methods ###
|
35
|
+
|
36
|
+
# Hash of the maximum size of the value for each column
|
37
|
+
def self.column_sizes(records, columns) # :nodoc:
|
38
|
+
sizes = Hash.new {0}
|
39
|
+
columns.each do |c|
|
40
|
+
s = c.to_s.size
|
41
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
42
|
+
end
|
43
|
+
records.each do |r|
|
44
|
+
columns.each do |c|
|
45
|
+
s = r[c].to_s.size
|
46
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
sizes
|
50
|
+
end
|
51
|
+
|
52
|
+
# String for each data line
|
53
|
+
def self.data_line(columns, sizes, record) # :nodoc:
|
54
|
+
'|' << columns.map {|c| format_cell(sizes[c], record[c])}.join('|') << '|'
|
55
|
+
end
|
56
|
+
|
57
|
+
# Format the value so it takes up exactly size characters
|
58
|
+
def self.format_cell(size, v) # :nodoc:
|
59
|
+
case v
|
60
|
+
when Bignum, Fixnum
|
61
|
+
"%#{size}d" % v
|
62
|
+
when Float
|
63
|
+
"%#{size}g" % v
|
64
|
+
else
|
65
|
+
"%-#{size}s" % v.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# String for header line
|
70
|
+
def self.header_line(columns, sizes) # :nodoc:
|
71
|
+
'|' << columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') << '|'
|
72
|
+
end
|
73
|
+
|
74
|
+
# String for separtor line
|
75
|
+
def self.separator_line(columns, sizes) # :nodoc:
|
76
|
+
'+' << columns.map {|c| '-' * sizes[c]}.join('+') << '+'
|
77
|
+
end
|
78
|
+
|
79
|
+
private_class_method :column_sizes, :data_line, :format_cell, :header_line, :separator_line
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# The query extension adds Sequel::Dataset#query which allows
|
2
|
+
# a different way to construct queries instead of the usual
|
3
|
+
# method chaining.
|
4
|
+
|
5
|
+
module Sequel
|
6
|
+
class Database
|
7
|
+
# Return a dataset modified by the query block
|
8
|
+
def query(&block)
|
9
|
+
dataset.query(&block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Dataset
|
14
|
+
# Translates a query block into a dataset. Query blocks can be useful
|
15
|
+
# when expressing complex SELECT statements, e.g.:
|
16
|
+
#
|
17
|
+
# dataset = DB[:items].query do
|
18
|
+
# select :x, :y, :z
|
19
|
+
# filter{|o| (o.x > 1) & (o.y > 2)}
|
20
|
+
# order :z.desc
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Which is the same as:
|
24
|
+
#
|
25
|
+
# dataset = DB[:items].select(:x, :y, :z).filter{|o| (o.x > 1) & (o.y > 2)}.order(:z.desc)
|
26
|
+
#
|
27
|
+
# Note that inside a call to query, you cannot call each, insert, update,
|
28
|
+
# or delete (or any method that calls those), or Sequel will raise an
|
29
|
+
# error.
|
30
|
+
def query(&block)
|
31
|
+
copy = clone({})
|
32
|
+
copy.extend(QueryBlockCopy)
|
33
|
+
copy.instance_eval(&block)
|
34
|
+
clone(copy.opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Module used by Dataset#query that has the effect of making all
|
38
|
+
# dataset methods into !-style methods that modify the receiver.
|
39
|
+
module QueryBlockCopy
|
40
|
+
%w'each insert update delete'.each do |meth|
|
41
|
+
define_method(meth){|*args| raise Error, "##{meth} cannot be invoked inside a query block."}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Merge the given options into the receiver's options and return the receiver
|
45
|
+
# instead of cloning the receiver.
|
46
|
+
def clone(opts = nil)
|
47
|
+
@opts.merge!(opts)
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# The schema_dumper extension supports dumping tables and indexes
|
2
|
+
# in a Sequel::Migration format, so they can be restored on another
|
3
|
+
# database (which can be the same type or a different type than
|
4
|
+
# the current database). The main interface is through
|
5
|
+
# Sequel::Database#dump_schema_migration.
|
6
|
+
|
7
|
+
module Sequel
|
8
|
+
class Database
|
9
|
+
# Dump indexes for all tables as a migration. This complements
|
10
|
+
# the :indexes=>false option to dump_schema_migration. Options:
|
11
|
+
# * :same_db - Create a dump for the same database type, so
|
12
|
+
# don't ignore errors if the index statements fail.
|
13
|
+
def dump_indexes_migration(options={})
|
14
|
+
ts = tables(options)
|
15
|
+
<<END_MIG
|
16
|
+
Class.new(Sequel::Migration) do
|
17
|
+
def up
|
18
|
+
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
|
19
|
+
end
|
20
|
+
|
21
|
+
def down
|
22
|
+
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
END_MIG
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return a string that contains a Sequel::Migration subclass that when
|
29
|
+
# run would recreate the database structure. Options:
|
30
|
+
# * :same_db - Don't attempt to translate database types to ruby types.
|
31
|
+
# If this isn't set to true, all database types will be translated to
|
32
|
+
# ruby types, but there is no guarantee that the migration generated
|
33
|
+
# will yield the same type. Without this set, types that aren't
|
34
|
+
# recognized will be translated to a string-like type.
|
35
|
+
# * :indexes - If set to false, don't dump indexes (they can be added
|
36
|
+
# later via dump_index_migration).
|
37
|
+
def dump_schema_migration(options={})
|
38
|
+
ts = tables(options)
|
39
|
+
<<END_MIG
|
40
|
+
Class.new(Sequel::Migration) do
|
41
|
+
def up
|
42
|
+
#{ts.sort_by{|t| t.to_s}.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}
|
43
|
+
end
|
44
|
+
|
45
|
+
def down
|
46
|
+
drop_table(#{ts.sort_by{|t| t.to_s}.inspect[1...-1]})
|
47
|
+
end
|
48
|
+
end
|
49
|
+
END_MIG
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a string with a create table block that will recreate the given
|
53
|
+
# table's schema. Takes the same options as dump_schema_migration.
|
54
|
+
def dump_table_schema(table, options={})
|
55
|
+
s = schema(table).dup
|
56
|
+
pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
|
57
|
+
options = options.merge(:single_pk=>true) if pks.length == 1
|
58
|
+
m = method(:column_schema_to_generator_opts)
|
59
|
+
im = method(:index_to_generator_opts)
|
60
|
+
indexes = indexes(table).sort_by{|k,v| k.to_s} if options[:indexes] != false and respond_to?(:indexes)
|
61
|
+
gen = Schema::Generator.new(self) do
|
62
|
+
s.each{|name, info| send(*m.call(name, info, options))}
|
63
|
+
primary_key(pks) if !@primary_key && pks.length > 0
|
64
|
+
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))} if indexes
|
65
|
+
end
|
66
|
+
commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n")
|
67
|
+
"create_table(#{table.inspect}#{', :ignore_index_errors=>true' if !options[:same_db] && options[:indexes] != false && indexes && !indexes.empty?}) do\n#{commands.gsub(/^/o, ' ')}\nend"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# If a database default exists and can't be converted, return the string with the inspect
|
73
|
+
# method modified so that .lit is always appended after it, only if the
|
74
|
+
# :same_db option is used.
|
75
|
+
def column_schema_to_ruby_default_fallback(default, options)
|
76
|
+
if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback?
|
77
|
+
default = default.to_s
|
78
|
+
def default.inspect
|
79
|
+
"#{super}.lit"
|
80
|
+
end
|
81
|
+
default
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Convert the given name and parsed database schema into an array with a method
|
86
|
+
# name and arguments to it to pass to a Schema::Generator to recreate the column.
|
87
|
+
def column_schema_to_generator_opts(name, schema, options)
|
88
|
+
if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
|
89
|
+
type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
|
90
|
+
if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
|
91
|
+
[:primary_key, name]
|
92
|
+
else
|
93
|
+
[:primary_key, name, type_hash]
|
94
|
+
end
|
95
|
+
else
|
96
|
+
col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
|
97
|
+
type = col_opts.delete(:type)
|
98
|
+
col_opts.delete(:size) if col_opts[:size].nil?
|
99
|
+
col_opts[:default] = if schema[:ruby_default].nil?
|
100
|
+
column_schema_to_ruby_default_fallback(schema[:default], options)
|
101
|
+
else
|
102
|
+
schema[:ruby_default]
|
103
|
+
end
|
104
|
+
col_opts.delete(:default) if col_opts[:default].nil?
|
105
|
+
col_opts[:null] = false if schema[:allow_null] == false
|
106
|
+
[:column, name, type, col_opts]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Convert the column schema information to a hash of column options, one of which must
|
111
|
+
# be :type. The other options added should modify that type (e.g. :size). If a
|
112
|
+
# database type is not recognized, return it as a String type.
|
113
|
+
def column_schema_to_ruby_type(schema)
|
114
|
+
case t = schema[:db_type].downcase
|
115
|
+
when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?\z/o
|
116
|
+
{:type=>Integer}
|
117
|
+
when /\Atinyint(?:\((\d+)\))?\z/o
|
118
|
+
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
119
|
+
when /\Abigint(?:\((?:\d+)\))?\z/o
|
120
|
+
{:type=>Bignum}
|
121
|
+
when /\A(?:real|float|double(?: precision)?)\z/o
|
122
|
+
{:type=>Float}
|
123
|
+
when 'boolean'
|
124
|
+
{:type=>TrueClass}
|
125
|
+
when /\A(?:(?:tiny|medium|long|n)?text|clob)\z/o
|
126
|
+
{:type=>String, :text=>true}
|
127
|
+
when 'date'
|
128
|
+
{:type=>Date}
|
129
|
+
when /\A(?:small)?datetime\z/o
|
130
|
+
{:type=>DateTime}
|
131
|
+
when /\Atimestamp(?: with(?:out)? time zone)?\z/o
|
132
|
+
{:type=>DateTime}
|
133
|
+
when /\Atime(?: with(?:out)? time zone)?\z/o
|
134
|
+
{:type=>Time, :only_time=>true}
|
135
|
+
when /\An?char(?:acter)?(?:\((\d+)\))?\z/o
|
136
|
+
{:type=>String, :size=>($1.to_i if $1), :fixed=>true}
|
137
|
+
when /\A(?:n?varchar|character varying|bpchar|string)(?:\((\d+)\))?\z/o
|
138
|
+
{:type=>String, :size=>($1.to_i if $1)}
|
139
|
+
when /\A(?:small)?money\z/o
|
140
|
+
{:type=>BigDecimal, :size=>[19,2]}
|
141
|
+
when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?\z/o
|
142
|
+
s = [($1.to_i if $1), ($2.to_i if $2)].compact
|
143
|
+
{:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
|
144
|
+
when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/o
|
145
|
+
{:type=>File, :size=>($1.to_i if $1)}
|
146
|
+
when 'year'
|
147
|
+
{:type=>Integer}
|
148
|
+
else
|
149
|
+
{:type=>String}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Return a string that containing add_index/drop_index method calls for
|
154
|
+
# creating the index migration.
|
155
|
+
def dump_table_indexes(table, meth, options={})
|
156
|
+
return '' unless respond_to?(:indexes)
|
157
|
+
im = method(:index_to_generator_opts)
|
158
|
+
indexes = indexes(table).sort_by{|k,v| k.to_s}
|
159
|
+
gen = Schema::Generator.new(self) do
|
160
|
+
indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts))}
|
161
|
+
end
|
162
|
+
gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db])
|
163
|
+
end
|
164
|
+
|
165
|
+
# Convert the parsed index information into options to the Generators index method.
|
166
|
+
def index_to_generator_opts(table, name, index_opts)
|
167
|
+
h = {}
|
168
|
+
h[:name] = name unless default_index_name(table, index_opts[:columns]) == name.to_s
|
169
|
+
h[:unique] = true if index_opts[:unique]
|
170
|
+
h
|
171
|
+
end
|
172
|
+
|
173
|
+
# Don't use the "...".lit fallback on MySQL, since the defaults it uses aren't
|
174
|
+
# valid literal SQL values.
|
175
|
+
def use_column_schema_to_ruby_default_fallback?
|
176
|
+
database_type != :mysql
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
module Schema
|
181
|
+
class Generator
|
182
|
+
# Dump this generator's columns to a string that could be evaled inside
|
183
|
+
# another instance to represent the same columns
|
184
|
+
def dump_columns
|
185
|
+
strings = []
|
186
|
+
cols = columns.dup
|
187
|
+
if pkn = primary_key_name
|
188
|
+
cols.delete_if{|x| x[:name] == pkn}
|
189
|
+
pk = @primary_key.dup
|
190
|
+
pkname = pk.delete(:name)
|
191
|
+
@db.serial_primary_key_options.each{|k,v| pk.delete(k) if v == pk[k]}
|
192
|
+
strings << "primary_key #{pkname.inspect}#{opts_inspect(pk)}"
|
193
|
+
end
|
194
|
+
cols.each do |c|
|
195
|
+
c = c.dup
|
196
|
+
name = c.delete(:name)
|
197
|
+
type = c.delete(:type)
|
198
|
+
opts = opts_inspect(c)
|
199
|
+
strings << if type.is_a?(Class)
|
200
|
+
"#{type.name} #{name.inspect}#{opts}"
|
201
|
+
else
|
202
|
+
"column #{name.inspect}, #{type.inspect}#{opts}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
strings.join("\n")
|
206
|
+
end
|
207
|
+
|
208
|
+
# Dump this generator's constraints to a string that could be evaled inside
|
209
|
+
# another instance to represent the same constraints
|
210
|
+
def dump_constraints
|
211
|
+
constraints.map do |c|
|
212
|
+
c = c.dup
|
213
|
+
type = c.delete(:type)
|
214
|
+
case type
|
215
|
+
when :check
|
216
|
+
raise(Error, "can't dump check/constraint specified with Proc") if c[:check].is_a?(Proc)
|
217
|
+
name = c.delete(:name)
|
218
|
+
if !name and c[:check].length == 1 and c[:check].first.is_a?(Hash)
|
219
|
+
"check #{c[:check].first.inspect[1...-1]}"
|
220
|
+
else
|
221
|
+
"#{name ? "constraint #{name.inspect}," : 'check'} #{c[:check].map{|x| x.inspect}.join(', ')}"
|
222
|
+
end
|
223
|
+
else
|
224
|
+
cols = c.delete(:columns)
|
225
|
+
"#{type} #{cols.inspect}#{opts_inspect(c)}"
|
226
|
+
end
|
227
|
+
end.join("\n")
|
228
|
+
end
|
229
|
+
|
230
|
+
# Dump this generator's indexes to a string that could be evaled inside
|
231
|
+
# another instance to represent the same indexes. Options:
|
232
|
+
# * :add_index - Use add_index instead of index, so the methods
|
233
|
+
# can be called outside of a generator but inside a migration.
|
234
|
+
# The value of this option should be the table name to use.
|
235
|
+
# * :drop_index - Same as add_index, but create drop_index statements.
|
236
|
+
# * :ignore_errors - Add the ignore_errors option to the outputted indexes
|
237
|
+
def dump_indexes(options={})
|
238
|
+
indexes.map do |c|
|
239
|
+
c = c.dup
|
240
|
+
cols = c.delete(:columns)
|
241
|
+
if table = options[:add_index] || options[:drop_index]
|
242
|
+
"#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{', :ignore_errors=>true' if options[:ignore_errors]}#{opts_inspect(c)}"
|
243
|
+
else
|
244
|
+
"index #{cols.inspect}#{opts_inspect(c)}"
|
245
|
+
end
|
246
|
+
end.join("\n")
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def opts_inspect(opts)
|
252
|
+
if opts[:default]
|
253
|
+
opts = opts.dup
|
254
|
+
de = case d = opts.delete(:default)
|
255
|
+
when BigDecimal, Sequel::SQL::Blob
|
256
|
+
"#{d.class.name}.new(#{d.to_s.inspect})"
|
257
|
+
when DateTime, Date
|
258
|
+
"#{d.class.name}.parse(#{d.to_s.inspect})"
|
259
|
+
when Time
|
260
|
+
"#{d.class.name}.parse(#{d.strftime('%H:%M:%S').inspect})"
|
261
|
+
else
|
262
|
+
d.inspect
|
263
|
+
end
|
264
|
+
", :default=>#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}"
|
265
|
+
else
|
266
|
+
", #{opts.inspect[1...-1]}" if opts.length > 0
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|