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.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. 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