sequel 4.45.0 → 4.46.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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +108 -0
  3. data/doc/release_notes/4.46.0.txt +404 -0
  4. data/doc/security.rdoc +9 -0
  5. data/doc/sql.rdoc +2 -2
  6. data/doc/testing.rdoc +1 -1
  7. data/doc/validations.rdoc +1 -2
  8. data/lib/sequel/adapters/ado.rb +8 -3
  9. data/lib/sequel/adapters/ado/access.rb +8 -4
  10. data/lib/sequel/adapters/ado/mssql.rb +3 -1
  11. data/lib/sequel/adapters/amalgalite.rb +5 -0
  12. data/lib/sequel/adapters/cubrid.rb +16 -7
  13. data/lib/sequel/adapters/do.rb +7 -1
  14. data/lib/sequel/adapters/do/mysql.rb +8 -4
  15. data/lib/sequel/adapters/ibmdb.rb +10 -5
  16. data/lib/sequel/adapters/jdbc.rb +8 -2
  17. data/lib/sequel/adapters/jdbc/as400.rb +10 -3
  18. data/lib/sequel/adapters/jdbc/db2.rb +27 -16
  19. data/lib/sequel/adapters/jdbc/derby.rb +47 -20
  20. data/lib/sequel/adapters/jdbc/h2.rb +13 -7
  21. data/lib/sequel/adapters/jdbc/hsqldb.rb +18 -9
  22. data/lib/sequel/adapters/jdbc/mssql.rb +5 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +3 -2
  24. data/lib/sequel/adapters/jdbc/oracle.rb +3 -2
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -3
  26. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +2 -1
  27. data/lib/sequel/adapters/jdbc/sqlite.rb +10 -3
  28. data/lib/sequel/adapters/jdbc/sqlserver.rb +23 -0
  29. data/lib/sequel/adapters/jdbc/transactions.rb +16 -10
  30. data/lib/sequel/adapters/mock.rb +5 -0
  31. data/lib/sequel/adapters/mysql.rb +8 -1
  32. data/lib/sequel/adapters/mysql2.rb +6 -1
  33. data/lib/sequel/adapters/odbc.rb +20 -8
  34. data/lib/sequel/adapters/odbc/mssql.rb +6 -3
  35. data/lib/sequel/adapters/oracle.rb +12 -6
  36. data/lib/sequel/adapters/postgres.rb +20 -8
  37. data/lib/sequel/adapters/shared/access.rb +76 -47
  38. data/lib/sequel/adapters/shared/cubrid.rb +16 -11
  39. data/lib/sequel/adapters/shared/db2.rb +46 -19
  40. data/lib/sequel/adapters/shared/firebird.rb +20 -8
  41. data/lib/sequel/adapters/shared/informix.rb +6 -3
  42. data/lib/sequel/adapters/shared/mssql.rb +132 -72
  43. data/lib/sequel/adapters/shared/mysql.rb +112 -65
  44. data/lib/sequel/adapters/shared/oracle.rb +36 -21
  45. data/lib/sequel/adapters/shared/postgres.rb +91 -56
  46. data/lib/sequel/adapters/shared/sqlanywhere.rb +65 -37
  47. data/lib/sequel/adapters/shared/sqlite.rb +67 -32
  48. data/lib/sequel/adapters/sqlanywhere.rb +9 -1
  49. data/lib/sequel/adapters/sqlite.rb +8 -1
  50. data/lib/sequel/adapters/swift.rb +5 -0
  51. data/lib/sequel/adapters/swift/mysql.rb +4 -2
  52. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  53. data/lib/sequel/adapters/tinytds.rb +10 -3
  54. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +1 -1
  55. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +1 -1
  56. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
  57. data/lib/sequel/adapters/utils/pg_types.rb +14 -6
  58. data/lib/sequel/adapters/utils/replace.rb +4 -2
  59. data/lib/sequel/connection_pool/single.rb +2 -2
  60. data/lib/sequel/core.rb +24 -11
  61. data/lib/sequel/database/connecting.rb +9 -3
  62. data/lib/sequel/database/dataset_defaults.rb +7 -1
  63. data/lib/sequel/database/logging.rb +1 -0
  64. data/lib/sequel/database/misc.rb +5 -2
  65. data/lib/sequel/database/query.rb +7 -5
  66. data/lib/sequel/database/schema_generator.rb +1 -0
  67. data/lib/sequel/database/schema_methods.rb +50 -27
  68. data/lib/sequel/database/transactions.rb +19 -9
  69. data/lib/sequel/dataset/actions.rb +15 -6
  70. data/lib/sequel/dataset/graph.rb +15 -5
  71. data/lib/sequel/dataset/misc.rb +12 -4
  72. data/lib/sequel/dataset/mutation.rb +17 -8
  73. data/lib/sequel/dataset/prepared_statements.rb +3 -2
  74. data/lib/sequel/dataset/query.rb +84 -38
  75. data/lib/sequel/dataset/sql.rb +302 -191
  76. data/lib/sequel/deprecated.rb +26 -17
  77. data/lib/sequel/extensions/_deprecated_identifier_mangling.rb +2 -2
  78. data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
  79. data/lib/sequel/extensions/from_block.rb +1 -0
  80. data/lib/sequel/extensions/graph_each.rb +1 -1
  81. data/lib/sequel/extensions/identifier_mangling.rb +2 -2
  82. data/lib/sequel/extensions/migration.rb +28 -4
  83. data/lib/sequel/extensions/no_auto_literal_strings.rb +2 -0
  84. data/lib/sequel/extensions/schema_dumper.rb +4 -4
  85. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +5 -3
  86. data/lib/sequel/extensions/set_overrides.rb +2 -0
  87. data/lib/sequel/extensions/split_array_nil.rb +2 -2
  88. data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
  89. data/lib/sequel/model.rb +11 -7
  90. data/lib/sequel/model/associations.rb +5 -7
  91. data/lib/sequel/model/base.rb +47 -45
  92. data/lib/sequel/model/dataset_module.rb +9 -14
  93. data/lib/sequel/model/plugins.rb +3 -0
  94. data/lib/sequel/no_core_ext.rb +1 -0
  95. data/lib/sequel/plugins/blacklist_security.rb +1 -1
  96. data/lib/sequel/plugins/boolean_subsets.rb +7 -5
  97. data/lib/sequel/plugins/class_table_inheritance.rb +47 -10
  98. data/lib/sequel/plugins/dataset_associations.rb +1 -1
  99. data/lib/sequel/plugins/def_dataset_method.rb +90 -0
  100. data/lib/sequel/plugins/finder.rb +240 -0
  101. data/lib/sequel/plugins/inverted_subsets.rb +19 -12
  102. data/lib/sequel/plugins/many_through_many.rb +1 -1
  103. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  104. data/lib/sequel/plugins/schema.rb +1 -1
  105. data/lib/sequel/plugins/single_table_inheritance.rb +7 -1
  106. data/lib/sequel/plugins/subset_conditions.rb +11 -3
  107. data/lib/sequel/plugins/whitelist_security.rb +118 -0
  108. data/lib/sequel/sql.rb +80 -36
  109. data/lib/sequel/timezones.rb +2 -0
  110. data/lib/sequel/version.rb +1 -1
  111. data/spec/adapters/mssql_spec.rb +20 -0
  112. data/spec/adapters/mysql_spec.rb +1 -1
  113. data/spec/adapters/oracle_spec.rb +12 -8
  114. data/spec/adapters/postgres_spec.rb +1 -1
  115. data/spec/adapters/spec_helper.rb +1 -1
  116. data/spec/adapters/sqlite_spec.rb +36 -34
  117. data/spec/core/connection_pool_spec.rb +2 -1
  118. data/spec/core/database_spec.rb +87 -9
  119. data/spec/core/dataset_spec.rb +501 -129
  120. data/spec/core/deprecated_spec.rb +1 -1
  121. data/spec/core/expression_filters_spec.rb +146 -60
  122. data/spec/core/mock_adapter_spec.rb +1 -1
  123. data/spec/core/object_graph_spec.rb +61 -9
  124. data/spec/core/placeholder_literalizer_spec.rb +20 -2
  125. data/spec/core/schema_generator_spec.rb +6 -6
  126. data/spec/core/schema_spec.rb +54 -5
  127. data/spec/core_extensions_spec.rb +122 -18
  128. data/spec/deprecation_helper.rb +27 -2
  129. data/spec/extensions/_deprecated_identifier_mangling_spec.rb +6 -6
  130. data/spec/extensions/association_proxies_spec.rb +2 -2
  131. data/spec/extensions/auto_literal_strings_spec.rb +212 -0
  132. data/spec/extensions/blacklist_security_spec.rb +1 -0
  133. data/spec/extensions/class_table_inheritance_spec.rb +1037 -39
  134. data/spec/extensions/column_select_spec.rb +20 -8
  135. data/spec/extensions/columns_introspection_spec.rb +3 -3
  136. data/spec/extensions/core_refinements_spec.rb +29 -12
  137. data/spec/extensions/dataset_associations_spec.rb +12 -12
  138. data/spec/extensions/def_dataset_method_spec.rb +100 -0
  139. data/spec/extensions/error_sql_spec.rb +1 -1
  140. data/spec/extensions/finder_spec.rb +260 -0
  141. data/spec/extensions/graph_each_spec.rb +2 -2
  142. data/spec/extensions/identifier_mangling_spec.rb +14 -8
  143. data/spec/extensions/inverted_subsets_spec.rb +4 -4
  144. data/spec/extensions/lazy_attributes_spec.rb +7 -0
  145. data/spec/extensions/many_through_many_spec.rb +38 -14
  146. data/spec/extensions/nested_attributes_spec.rb +18 -6
  147. data/spec/extensions/no_auto_literal_strings_spec.rb +1 -1
  148. data/spec/extensions/pg_enum_spec.rb +16 -1
  149. data/spec/extensions/pg_interval_spec.rb +11 -2
  150. data/spec/extensions/pg_loose_count_spec.rb +5 -0
  151. data/spec/extensions/pg_row_spec.rb +25 -0
  152. data/spec/extensions/prepared_statements_spec.rb +10 -1
  153. data/spec/extensions/query_spec.rb +2 -2
  154. data/spec/extensions/schema_dumper_spec.rb +2 -2
  155. data/spec/extensions/schema_spec.rb +2 -2
  156. data/spec/extensions/set_overrides_spec.rb +7 -3
  157. data/spec/extensions/sql_expr_spec.rb +0 -1
  158. data/spec/extensions/subset_conditions_spec.rb +6 -6
  159. data/spec/extensions/table_select_spec.rb +24 -12
  160. data/spec/extensions/to_dot_spec.rb +4 -4
  161. data/spec/extensions/whitelist_security_spec.rb +131 -0
  162. data/spec/integration/dataset_test.rb +9 -5
  163. data/spec/integration/model_test.rb +2 -0
  164. data/spec/integration/plugin_test.rb +2 -2
  165. data/spec/integration/spec_helper.rb +1 -1
  166. data/spec/model/associations_spec.rb +39 -11
  167. data/spec/model/base_spec.rb +44 -24
  168. data/spec/model/class_dataset_methods_spec.rb +18 -16
  169. data/spec/model/dataset_methods_spec.rb +4 -4
  170. data/spec/model/eager_loading_spec.rb +84 -24
  171. data/spec/model/model_spec.rb +97 -63
  172. data/spec/model/record_spec.rb +21 -13
  173. metadata +13 -2
@@ -24,26 +24,29 @@ describe "Sequel::Plugins::ColumnSelect" do
24
24
  @Album.dataset.sql.must_equal 'SELECT albs.id, albs.a, albs.b, albs.c FROM albs'
25
25
  end
26
26
 
27
- it "should handle qualified tables" do
27
+ with_symbol_splitting "should handle splittable symbols" do
28
28
  @Album.dataset = :s__albums
29
29
  @Album.plugin :column_select
30
30
  @Album.dataset.sql.must_equal 'SELECT s.albums.id, s.albums.a, s.albums.b, s.albums.c FROM s.albums'
31
31
 
32
+ @Album.dataset = :albums___a
33
+ @Album.dataset.sql.must_equal 'SELECT a.id, a.a, a.b, a.c FROM albums AS a'
34
+
35
+ @Album.dataset = :s__albums___a
36
+ @Album.dataset.sql.must_equal 'SELECT a.id, a.a, a.b, a.c FROM s.albums AS a'
37
+ end
38
+
39
+ it "should handle qualified tables" do
32
40
  @Album.dataset = Sequel.qualify(:s2, :albums)
41
+ @Album.plugin :column_select
33
42
  @Album.dataset.sql.must_equal 'SELECT s2.albums.id, s2.albums.a, s2.albums.b, s2.albums.c FROM s2.albums'
34
43
  end
35
44
 
36
45
  it "should handle aliases" do
37
- @Album.dataset = :albums___a
38
46
  @Album.plugin :column_select
39
- @Album.dataset.sql.must_equal 'SELECT a.id, a.a, a.b, a.c FROM albums AS a'
40
-
41
47
  @Album.dataset = Sequel.as(:albums, :b)
42
48
  @Album.dataset.sql.must_equal 'SELECT b.id, b.a, b.b, b.c FROM albums AS b'
43
49
 
44
- @Album.dataset = :s__albums___a
45
- @Album.dataset.sql.must_equal 'SELECT a.id, a.a, a.b, a.c FROM s.albums AS a'
46
-
47
50
  @Album.dataset = @Album.db[:albums].from_self
48
51
  @Album.dataset.sql.must_equal 'SELECT t1.id, t1.a, t1.b, t1.c FROM (SELECT * FROM albums) AS t1'
49
52
 
@@ -60,7 +63,7 @@ describe "Sequel::Plugins::ColumnSelect" do
60
63
  @Album.dataset.sql.must_equal 'SELECT name, artist FROM albums'
61
64
  end
62
65
 
63
- it "should not add a explicit column selection on existing dataset with multiple tables" do
66
+ deprecated "should not add a explicit column selection on existing dataset with multiple tables" do
64
67
  @Album.dataset = @Album.db.from(:a1, :a2)
65
68
  @Album.plugin :column_select
66
69
  @Album.dataset.sql.must_equal 'SELECT * FROM a1, a2'
@@ -69,6 +72,15 @@ describe "Sequel::Plugins::ColumnSelect" do
69
72
  @Album.dataset.sql.must_equal 'SELECT * FROM a1 CROSS JOIN a2'
70
73
  end
71
74
 
75
+ it "should add a explicit column selection on existing dataset with a subquery" do
76
+ @Album.dataset = @Album.db.from(:a1, :a2).from_self(:alias=>:foo)
77
+ @Album.plugin :column_select
78
+ @Album.dataset.sql.must_equal 'SELECT foo.id, foo.a, foo.b, foo.c FROM (SELECT * FROM a1, a2) AS foo'
79
+
80
+ @Album.dataset = @Album.db.from(:a1).cross_join(:a2).from_self(:alias=>:foo)
81
+ @Album.dataset.sql.must_equal 'SELECT foo.id, foo.a, foo.b, foo.c FROM (SELECT * FROM a1 CROSS JOIN a2) AS foo'
82
+ end
83
+
72
84
  it "should use explicit column selection for many_to_many associations" do
73
85
  @Album.plugin :column_select
74
86
  @Album.many_to_many :albums, :class=>@Album, :left_key=>:l, :right_key=>:r, :join_table=>:j
@@ -20,17 +20,17 @@ describe "columns_introspection extension" do
20
20
  @db.sqls.length.must_equal 0
21
21
  end
22
22
 
23
- it "should handle qualified symbols without a database query" do
23
+ with_symbol_splitting "should handle qualified symbols without a database query" do
24
24
  @ds.select(:t__x).columns.must_equal [:x]
25
25
  @db.sqls.length.must_equal 0
26
26
  end
27
27
 
28
- it "should handle aliased symbols without a database query" do
28
+ with_symbol_splitting "should handle aliased symbols without a database query" do
29
29
  @ds.select(:x___a).columns.must_equal [:a]
30
30
  @db.sqls.length.must_equal 0
31
31
  end
32
32
 
33
- it "should handle qualified and aliased symbols without a database query" do
33
+ with_symbol_splitting "should handle qualified and aliased symbols without a database query" do
34
34
  @ds.select(:t__x___a).columns.must_equal [:a]
35
35
  @db.sqls.length.must_equal 0
36
36
  end
@@ -20,6 +20,9 @@ describe "Core refinements" do
20
20
 
21
21
  it "should support NOT via Symbol#~" do
22
22
  @d.l(~:x).must_equal 'NOT x'
23
+ end
24
+
25
+ with_symbol_splitting "should support NOT via Symbol#~ for splittable symbols" do
23
26
  @d.l(~:x__y).must_equal 'NOT x.y'
24
27
  end
25
28
 
@@ -97,7 +100,7 @@ describe "Core refinements" do
97
100
  @d.lit([:x.sql_function(1), 'y.z'.lit].sql_string_join(', ')).must_equal "(x(1) || ', ' || y.z)"
98
101
  @d.lit([:x, 1, :y].sql_string_join).must_equal "(x || '1' || y)"
99
102
  @d.lit([:x, 1, :y].sql_string_join(', ')).must_equal "(x || ', ' || '1' || ', ' || y)"
100
- @d.lit([:x, 1, :y].sql_string_join(:y__z)).must_equal "(x || y.z || '1' || y.z || y)"
103
+ @d.lit([:x, 1, :y].sql_string_join(Sequel[:y][:z])).must_equal "(x || y.z || '1' || y.z || y)"
101
104
  @d.lit([:x, 1, :y].sql_string_join(1)).must_equal "(x || '1' || '1' || '1' || y)"
102
105
  @d.lit([:x, :y].sql_string_join('y.x || x.y'.lit)).must_equal "(x || y.x || x.y || y)"
103
106
  @d.lit([[:x, :y].sql_string_join, [:a, :b].sql_string_join].sql_string_join).must_equal "(x || y || a || b)"
@@ -148,7 +151,7 @@ describe "Array#case and Hash#case" do
148
151
  @d.literal([[:x, :y]].case(:z)).must_equal '(CASE WHEN x THEN y ELSE z END)'
149
152
  @d.literal([[:x, :y], [:a, :b]].case(:z)).must_equal '(CASE WHEN x THEN y WHEN a THEN b ELSE z END)'
150
153
  @d.literal([[:x, :y], [:a, :b]].case(:z, :exp)).must_equal '(CASE exp WHEN x THEN y WHEN a THEN b ELSE z END)'
151
- @d.literal([[:x, :y], [:a, :b]].case(:z, :exp__w)).must_equal '(CASE exp.w WHEN x THEN y WHEN a THEN b ELSE z END)'
154
+ @d.literal([[:x, :y], [:a, :b]].case(:z, Sequel[:exp][:w])).must_equal '(CASE exp.w WHEN x THEN y WHEN a THEN b ELSE z END)'
152
155
  end
153
156
 
154
157
  it "should return SQL CASE expression with expression even if nil" do
@@ -173,8 +176,8 @@ describe "Array#sql_value_list and #sql_array" do
173
176
  end
174
177
 
175
178
  it "should treat the array as an SQL value list instead of conditions when used as a placeholder value" do
176
- @d.filter("(a, b) IN ?", [[:x, 1], [:y, 2]]).sql.must_equal 'SELECT * WHERE ((a, b) IN ((x = 1) AND (y = 2)))'
177
- @d.filter("(a, b) IN ?", [[:x, 1], [:y, 2]].sql_value_list).sql.must_equal 'SELECT * WHERE ((a, b) IN ((x, 1), (y, 2)))'
179
+ @d.filter(Sequel.lit("(a, b) IN ?", [[:x, 1], [:y, 2]])).sql.must_equal 'SELECT * WHERE ((a, b) IN ((x = 1) AND (y = 2)))'
180
+ @d.filter(Sequel.lit("(a, b) IN ?", [[:x, 1], [:y, 2]].sql_value_list)).sql.must_equal 'SELECT * WHERE ((a, b) IN ((x, 1), (y, 2)))'
178
181
  end
179
182
 
180
183
  it "should be no difference when used as a hash value" do
@@ -237,7 +240,9 @@ describe "#desc" do
237
240
 
238
241
  it "should format a DESC clause for a column ref" do
239
242
  @ds.literal(:test.desc).must_equal 'test DESC'
243
+ end
240
244
 
245
+ with_symbol_splitting "should format a DESC clause for a column ref with splittable symbol" do
241
246
  @ds.literal(:items__price.desc).must_equal 'items.price DESC'
242
247
  end
243
248
 
@@ -253,7 +258,9 @@ describe "#asc" do
253
258
 
254
259
  it "should format a ASC clause for a column ref" do
255
260
  @ds.literal(:test.asc).must_equal 'test ASC'
261
+ end
256
262
 
263
+ with_symbol_splitting "should format a ASC clause for a column ref with splittable symbol" do
257
264
  @ds.literal(:items__price.asc).must_equal 'items.price ASC'
258
265
  end
259
266
 
@@ -269,7 +276,9 @@ describe "#as" do
269
276
 
270
277
  it "should format a AS clause for a column ref" do
271
278
  @ds.literal(:test.as(:t)).must_equal 'test AS t'
279
+ end
272
280
 
281
+ with_symbol_splitting "should format a AS clause for a column ref with splittable symbols" do
273
282
  @ds.literal(:items__price.as(:p)).must_equal 'items.price AS p'
274
283
  end
275
284
 
@@ -289,13 +298,7 @@ describe "Column references" do
289
298
 
290
299
  it "should be quoted properly" do
291
300
  @ds.literal(:xyz).must_equal "`xyz`"
292
- @ds.literal(:xyz__abc).must_equal "`xyz`.`abc`"
293
-
294
301
  @ds.literal(:xyz.as(:x)).must_equal "`xyz` AS `x`"
295
- @ds.literal(:xyz__abc.as(:x)).must_equal "`xyz`.`abc` AS `x`"
296
-
297
- @ds.literal(:xyz___x).must_equal "`xyz` AS `x`"
298
- @ds.literal(:xyz__abc___x).must_equal "`xyz`.`abc` AS `x`"
299
302
  end
300
303
 
301
304
  it "should be quoted properly in SQL functions" do
@@ -311,6 +314,13 @@ describe "Column references" do
311
314
 
312
315
  it "should be quoted properly in a cast function" do
313
316
  @ds.literal(:x.cast(:integer)).must_equal "CAST(`x` AS integer)"
317
+ end
318
+
319
+ with_symbol_splitting "should be quoted properly when using splittable symbols" do
320
+ @ds.literal(:xyz__abc).must_equal "`xyz`.`abc`"
321
+ @ds.literal(:xyz__abc.as(:x)).must_equal "`xyz`.`abc` AS `x`"
322
+ @ds.literal(:xyz___x).must_equal "`xyz` AS `x`"
323
+ @ds.literal(:xyz__abc___x).must_equal "`xyz`.`abc` AS `x`"
314
324
  @ds.literal(:x__y.cast('varchar(20)')).must_equal "CAST(`x`.`y` AS varchar(20))"
315
325
  end
316
326
  end
@@ -337,7 +347,7 @@ describe "Symbol#*" do
337
347
  @ds.literal(:abc.*(5)).must_equal '(abc * 5)'
338
348
  end
339
349
 
340
- it "should support qualified symbols if no argument" do
350
+ with_symbol_splitting "should support qualified symbols if no argument" do
341
351
  @ds.literal(:xyz__abc.*).must_equal 'xyz.abc.*'
342
352
  end
343
353
  end
@@ -360,6 +370,10 @@ describe "Symbol" do
360
370
  end
361
371
 
362
372
  it "should be able to qualify an identifier" do
373
+ @ds.literal(:xyz.identifier.qualify(Sequel[:xyz][:abc])).must_equal '"xyz"."abc"."xyz"'
374
+ end
375
+
376
+ with_symbol_splitting "should be able to qualify an identifier with qualified symbol" do
363
377
  @ds.literal(:xyz.identifier.qualify(:xyz__abc)).must_equal '"xyz"."abc"."xyz"'
364
378
  end
365
379
 
@@ -398,9 +412,12 @@ describe "Symbol" do
398
412
  @ds.literal(:abc.cast(:integer)).must_equal "CAST(abc AS integer)"
399
413
  end
400
414
 
415
+ with_symbol_splitting "should support sql array accesses via sql_subscript for splittable symbols" do
416
+ @ds.literal(:abc__def.sql_subscript(1)).must_equal "abc.def[1]"
417
+ end
418
+
401
419
  it "should support sql array accesses via sql_subscript" do
402
420
  @ds.literal(:abc.sql_subscript(1)).must_equal "abc[1]"
403
- @ds.literal(:abc__def.sql_subscript(1)).must_equal "abc.def[1]"
404
421
  @ds.literal(:abc.sql_subscript(1)|2).must_equal "abc[1, 2]"
405
422
  @ds.literal(:abc.sql_subscript(1)[2]).must_equal "abc[1][2]"
406
423
  end
@@ -100,7 +100,7 @@ describe "Sequel::Plugins::DatasetAssociations" do
100
100
  end
101
101
 
102
102
  it "should work for many_to_many associations with :dataset_association_join=>true" do
103
- @Album.many_to_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
103
+ @Album.many_to_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, Sequel[:albums_tags][:foo]]
104
104
  ds = @Album.tags
105
105
  ds.must_be_kind_of(Sequel::Dataset)
106
106
  ds.model.must_equal @Tag
@@ -108,7 +108,7 @@ describe "Sequel::Plugins::DatasetAssociations" do
108
108
  end
109
109
 
110
110
  it "should work for one_through_one associations with :dataset_association_join=>true" do
111
- @Album.one_through_one :first_tag, :clone=>:first_tag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
111
+ @Album.one_through_one :first_tag, :clone=>:first_tag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, Sequel[:albums_tags][:foo]]
112
112
  ds = @Album.first_tags
113
113
  ds.must_be_kind_of(Sequel::Dataset)
114
114
  ds.model.must_equal @Tag
@@ -116,7 +116,7 @@ describe "Sequel::Plugins::DatasetAssociations" do
116
116
  end
117
117
 
118
118
  it "should work for many_through_many associations with :dataset_association_join=>true" do
119
- @Artist.many_through_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
119
+ @Artist.many_through_many :tags, :clone=>:tags, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, Sequel[:albums_tags][:foo]]
120
120
  ds = @Artist.tags
121
121
  ds.must_be_kind_of(Sequel::Dataset)
122
122
  ds.model.must_equal @Tag
@@ -124,7 +124,7 @@ describe "Sequel::Plugins::DatasetAssociations" do
124
124
  end
125
125
 
126
126
  it "should work for one_through_many associations with :dataset_association_join=>true" do
127
- @Artist.one_through_many :otag, :clone=>:otag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, :albums_tags__foo]
127
+ @Artist.one_through_many :otag, :clone=>:otag, :dataset_associations_join=>true, :select=>[Sequel.expr(:tags).*, Sequel[:albums_tags][:foo]]
128
128
  ds = @Artist.otags
129
129
  ds.must_be_kind_of(Sequel::Dataset)
130
130
  ds.model.must_equal @Tag
@@ -227,22 +227,22 @@ describe "Sequel::Plugins::DatasetAssociations" do
227
227
  end
228
228
 
229
229
  it "should deal correctly with :order option for one_through_one associations" do
230
- @Album.one_through_one :first_tag, :clone=>:first_tag, :order=>:tags__name
230
+ @Album.one_through_one :first_tag, :clone=>:first_tag, :order=>Sequel[:tags][:name]
231
231
  @Album.first_tags.sql.must_equal 'SELECT tags.* FROM tags WHERE (tags.id IN (SELECT albums_tags.tag_id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (((albums_tags.album_id) IN (SELECT albums.id FROM albums)) AND ((albums_tags.album_id, tags.id) IN (SELECT DISTINCT ON (albums_tags.album_id) albums_tags.album_id, tags.id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) ORDER BY albums_tags.album_id, tags.name))))) ORDER BY tags.name'
232
232
  end
233
233
 
234
234
  it "should deal correctly with :limit option for many_to_many associations" do
235
- @Album.many_to_many :tags, :clone=>:tags, :limit=>10, :order=>:tags__name
235
+ @Album.many_to_many :tags, :clone=>:tags, :limit=>10, :order=>Sequel[:tags][:name]
236
236
  @Album.tags.sql.must_equal 'SELECT tags.* FROM tags WHERE (tags.id IN (SELECT albums_tags.tag_id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) WHERE (((albums_tags.album_id) IN (SELECT albums.id FROM albums)) AND ((albums_tags.album_id, tags.id) IN (SELECT b, c FROM (SELECT albums_tags.album_id AS b, tags.id AS c, row_number() OVER (PARTITION BY albums_tags.album_id ORDER BY tags.name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id)) AS t1 WHERE (x_sequel_row_number_x <= 10)))))) ORDER BY tags.name'
237
237
  end
238
238
 
239
239
  it "should deal correctly with :order option for one_through_many associations" do
240
- @Artist.one_through_many :otag, :clone=>:otag, :order=>:tags__name
240
+ @Artist.one_through_many :otag, :clone=>:otag, :order=>Sequel[:tags][:name]
241
241
  @Artist.otags.sql.must_equal 'SELECT tags.* FROM tags WHERE (tags.id IN (SELECT albums_tags.tag_id FROM artists INNER JOIN albums ON (albums.artist_id = artists.id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id) WHERE ((albums.artist_id IN (SELECT artists.id FROM artists)) AND ((albums.artist_id, tags.id) IN (SELECT DISTINCT ON (albums.artist_id) albums.artist_id, tags.id FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) ORDER BY albums.artist_id, tags.name))))) ORDER BY tags.name'
242
242
  end
243
243
 
244
244
  it "should deal correctly with :limit option for many_through_many associations" do
245
- @Artist.many_through_many :tags, :clone=>:tags, :limit=>10, :order=>:tags__name
245
+ @Artist.many_through_many :tags, :clone=>:tags, :limit=>10, :order=>Sequel[:tags][:name]
246
246
  @Artist.tags.sql.must_equal 'SELECT tags.* FROM tags WHERE (tags.id IN (SELECT albums_tags.tag_id FROM artists INNER JOIN albums ON (albums.artist_id = artists.id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) INNER JOIN tags ON (tags.id = albums_tags.tag_id) WHERE ((albums.artist_id IN (SELECT artists.id FROM artists)) AND ((albums.artist_id, tags.id) IN (SELECT b, c FROM (SELECT albums.artist_id AS b, tags.id AS c, row_number() OVER (PARTITION BY albums.artist_id ORDER BY tags.name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id)) AS t1 WHERE (x_sequel_row_number_x <= 10)))))) ORDER BY tags.name'
247
247
  end
248
248
  end
@@ -331,22 +331,22 @@ describe "Sequel::Plugins::DatasetAssociations with composite keys" do
331
331
  end
332
332
 
333
333
  it "should deal correctly with :order option for one_through_one associations" do
334
- @Album.one_through_one :first_tag, :clone=>:first_tag, :order=>:tags__name
334
+ @Album.one_through_one :first_tag, :clone=>:first_tag, :order=>Sequel[:tags][:name]
335
335
  @Album.first_tags.sql.must_equal 'SELECT tags.* FROM tags WHERE ((tags.id1, tags.id2) IN (SELECT albums_tags.tag_id1, albums_tags.tag_id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2)) WHERE (((albums_tags.album_id1, albums_tags.album_id2) IN (SELECT albums.id1, albums.id2 FROM albums)) AND ((albums_tags.album_id1, albums_tags.album_id2, tags.id1, tags.id2) IN (SELECT DISTINCT ON (albums_tags.album_id1, albums_tags.album_id2) albums_tags.album_id1, albums_tags.album_id2, tags.id1, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2)) ORDER BY albums_tags.album_id1, albums_tags.album_id2, tags.name))))) ORDER BY tags.name'
336
336
  end
337
337
 
338
338
  it "should deal correctly with :limit option for many_to_many associations" do
339
- @Album.many_to_many :tags, :clone=>:tags, :limit=>10, :order=>:tags__name
339
+ @Album.many_to_many :tags, :clone=>:tags, :limit=>10, :order=>Sequel[:tags][:name]
340
340
  @Album.tags.sql.must_equal 'SELECT tags.* FROM tags WHERE ((tags.id1, tags.id2) IN (SELECT albums_tags.tag_id1, albums_tags.tag_id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2)) WHERE (((albums_tags.album_id1, albums_tags.album_id2) IN (SELECT albums.id1, albums.id2 FROM albums)) AND ((albums_tags.album_id1, albums_tags.album_id2, tags.id1, tags.id2) IN (SELECT b, c, d, e FROM (SELECT albums_tags.album_id1 AS b, albums_tags.album_id2 AS c, tags.id1 AS d, tags.id2 AS e, row_number() OVER (PARTITION BY albums_tags.album_id1, albums_tags.album_id2 ORDER BY tags.name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2))) AS t1 WHERE (x_sequel_row_number_x <= 10)))))) ORDER BY tags.name'
341
341
  end
342
342
 
343
343
  it "should deal correctly with :order option for one_through_many associations" do
344
- @Artist.one_through_many :otag, :clone=>:otag, :order=>:tags__name
344
+ @Artist.one_through_many :otag, :clone=>:otag, :order=>Sequel[:tags][:name]
345
345
  @Artist.otags.sql.must_equal 'SELECT tags.* FROM tags WHERE ((tags.id1, tags.id2) IN (SELECT albums_tags.tag_id1, albums_tags.tag_id2 FROM artists INNER JOIN albums ON ((albums.artist_id1 = artists.id1) AND (albums.artist_id2 = artists.id2)) INNER JOIN albums_tags ON ((albums_tags.album_id1 = albums.id1) AND (albums_tags.album_id2 = albums.id2)) INNER JOIN tags ON ((tags.id1 = albums_tags.tag_id1) AND (tags.id2 = albums_tags.tag_id2)) WHERE (((albums.artist_id1, albums.artist_id2) IN (SELECT artists.id1, artists.id2 FROM artists)) AND ((albums.artist_id1, albums.artist_id2, tags.id1, tags.id2) IN (SELECT DISTINCT ON (albums.artist_id1, albums.artist_id2) albums.artist_id1, albums.artist_id2, tags.id1, tags.id2 FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id1 = albums_tags.album_id1) AND (albums.id2 = albums_tags.album_id2)) ORDER BY albums.artist_id1, albums.artist_id2, tags.name))))) ORDER BY tags.name'
346
346
  end
347
347
 
348
348
  it "should deal correctly with :limit option for many_through_many associations" do
349
- @Artist.many_through_many :tags, :clone=>:tags, :limit=>10, :order=>:tags__name
349
+ @Artist.many_through_many :tags, :clone=>:tags, :limit=>10, :order=>Sequel[:tags][:name]
350
350
  @Artist.tags.sql.must_equal 'SELECT tags.* FROM tags WHERE ((tags.id1, tags.id2) IN (SELECT albums_tags.tag_id1, albums_tags.tag_id2 FROM artists INNER JOIN albums ON ((albums.artist_id1 = artists.id1) AND (albums.artist_id2 = artists.id2)) INNER JOIN albums_tags ON ((albums_tags.album_id1 = albums.id1) AND (albums_tags.album_id2 = albums.id2)) INNER JOIN tags ON ((tags.id1 = albums_tags.tag_id1) AND (tags.id2 = albums_tags.tag_id2)) WHERE (((albums.artist_id1, albums.artist_id2) IN (SELECT artists.id1, artists.id2 FROM artists)) AND ((albums.artist_id1, albums.artist_id2, tags.id1, tags.id2) IN (SELECT b, c, d, e FROM (SELECT albums.artist_id1 AS b, albums.artist_id2 AS c, tags.id1 AS d, tags.id2 AS e, row_number() OVER (PARTITION BY albums.artist_id1, albums.artist_id2 ORDER BY tags.name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id1 = tags.id1) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id1 = albums_tags.album_id1) AND (albums.id2 = albums_tags.album_id2))) AS t1 WHERE (x_sequel_row_number_x <= 10)))))) ORDER BY tags.name'
351
351
  end
352
352
  end
@@ -0,0 +1,100 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe Sequel::Model, ".def_dataset_method" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:items))
6
+ @c.plugin :def_dataset_method
7
+ end
8
+
9
+ it "should add a method to the dataset and model if called with a block argument" do
10
+ @c.def_dataset_method(:return_3){3}
11
+ @c.return_3.must_equal 3
12
+ @c.dataset.return_3.must_equal 3
13
+ end
14
+
15
+ it "should handle weird method names" do
16
+ @c.def_dataset_method(:"return 3"){3}
17
+ @c.send(:"return 3").must_equal 3
18
+ @c.dataset.send(:"return 3").must_equal 3
19
+ end
20
+
21
+ it "should not add a model method if the model already responds to the method" do
22
+ @c.instance_eval do
23
+ def foo
24
+ 1
25
+ end
26
+
27
+ private
28
+
29
+ def bar
30
+ 2
31
+ end
32
+
33
+ def_dataset_method(:foo){3}
34
+ def_dataset_method(:bar){4}
35
+ end
36
+ @c.foo.must_equal 1
37
+ @c.dataset.foo.must_equal 3
38
+ @c.send(:bar).must_equal 2
39
+ @c.dataset.bar.must_equal 4
40
+ end
41
+
42
+ it "should add all passed methods to the model if called without a block argument" do
43
+ @c.def_dataset_method(:return_3, :return_4)
44
+ proc{@c.return_3}.must_raise(NoMethodError)
45
+ proc{@c.return_4}.must_raise(NoMethodError)
46
+ @c.dataset = @c.dataset.with_extend do
47
+ def return_3; 3; end
48
+ def return_4; 4; end
49
+ end
50
+ @c.return_3.must_equal 3
51
+ @c.return_4.must_equal 4
52
+ end
53
+
54
+ it "should cache calls and readd methods if set_dataset is used" do
55
+ @c.def_dataset_method(:return_3){3}
56
+ @c.set_dataset :items
57
+ @c.return_3.must_equal 3
58
+ @c.dataset.return_3.must_equal 3
59
+ end
60
+
61
+ it "should readd methods to subclasses, if set_dataset is used in a subclass" do
62
+ @c.def_dataset_method(:return_3){3}
63
+ c = Class.new(@c)
64
+ c.set_dataset :items
65
+ c.return_3.must_equal 3
66
+ c.dataset.return_3.must_equal 3
67
+ end
68
+ end
69
+
70
+ describe Sequel::Model, ".subset" do
71
+ before do
72
+ @c = Class.new(Sequel::Model(:items))
73
+ @c.plugin :def_dataset_method
74
+ DB.reset
75
+ end
76
+
77
+ it "should create a filter on the underlying dataset" do
78
+ proc {@c.new_only}.must_raise(NoMethodError)
79
+
80
+ @c.subset(:new_only){age < 'new'}
81
+
82
+ @c.new_only.sql.must_equal "SELECT * FROM items WHERE (age < 'new')"
83
+ @c.dataset.new_only.sql.must_equal "SELECT * FROM items WHERE (age < 'new')"
84
+
85
+ @c.subset(:pricey){price > 100}
86
+
87
+ @c.pricey.sql.must_equal "SELECT * FROM items WHERE (price > 100)"
88
+ @c.dataset.pricey.sql.must_equal "SELECT * FROM items WHERE (price > 100)"
89
+
90
+ @c.pricey.new_only.sql.must_equal "SELECT * FROM items WHERE ((price > 100) AND (age < 'new'))"
91
+ @c.new_only.pricey.sql.must_equal "SELECT * FROM items WHERE ((age < 'new') AND (price > 100))"
92
+ end
93
+
94
+ it "should not override existing model methods" do
95
+ def @c.active() true end
96
+ @c.subset(:active, :active)
97
+ @c.active.must_equal true
98
+ end
99
+ end
100
+
@@ -2,7 +2,7 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "error_sql extension" do
4
4
  before do
5
- @db = Sequel.mock(:fetch=>proc{|sql| @db.log_yield(sql){raise StandardError}}).extension(:error_sql)
5
+ @db = Sequel.mock(:fetch=>proc{|sql| @db.log_connection_yield(sql, nil){raise StandardError}}).extension(:error_sql)
6
6
  end
7
7
 
8
8
  it "should have Sequel::DatabaseError#sql give the SQL causing the error" do
@@ -0,0 +1,260 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe Sequel::Model, ".finder" do
4
+ before do
5
+ @h = {:id=>1}
6
+ @db = Sequel.mock(:fetch=>@h)
7
+ @c = Class.new(Sequel::Model(@db[:items]))
8
+ @c.instance_eval do
9
+ def foo(a, b)
10
+ where(:bar=>a).order(b)
11
+ end
12
+ end
13
+ @c.plugin :finder
14
+ @o = @c.load(@h)
15
+ @db.sqls
16
+ end
17
+
18
+ it "should create a method that calls the method given and returns the first instance" do
19
+ @c.finder :foo
20
+ @c.first_foo(1, 2).must_equal @o
21
+ @c.first_foo(3, 4).must_equal @o
22
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
23
+ end
24
+
25
+ it "should work correctly when subclassing" do
26
+ @c.finder(:foo)
27
+ @sc = Class.new(@c)
28
+ @sc.set_dataset :foos
29
+ @db.sqls
30
+ @sc.first_foo(1, 2).must_equal @sc.load(@h)
31
+ @sc.first_foo(3, 4).must_equal @sc.load(@h)
32
+ @db.sqls.must_equal ["SELECT * FROM foos WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
33
+ end
34
+
35
+ it "should work correctly when dataset is modified" do
36
+ @c.finder(:foo)
37
+ @c.first_foo(1, 2).must_equal @o
38
+ @c.set_dataset :foos
39
+ @c.first_foo(3, 4).must_equal @o
40
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM foos LIMIT 1", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
41
+ end
42
+
43
+ it "should create a method based on the given block if no method symbol provided" do
44
+ @c.finder(:name=>:first_foo){|pl, ds| ds.where(pl.arg).limit(1)}
45
+ @c.first_foo(:id=>1).must_equal @o
46
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (id = 1) LIMIT 1"]
47
+ end
48
+
49
+ it "should raise an error if both a block and method symbol given" do
50
+ proc{@c.finder(:foo, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.must_raise(Sequel::Error)
51
+ end
52
+
53
+ it "should raise an error if two option hashes are provided" do
54
+ proc{@c.finder({:name2=>:foo}, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.must_raise(Sequel::Error)
55
+ end
56
+
57
+ it "should support :type option" do
58
+ @c.finder :foo, :type=>:all
59
+ @c.finder :foo, :type=>:each
60
+ @c.finder :foo, :type=>:get
61
+
62
+ a = []
63
+ @c.all_foo(1, 2){|r| a << r}.must_equal [@o]
64
+ a.must_equal [@o]
65
+
66
+ a = []
67
+ @c.each_foo(3, 4){|r| a << r}
68
+ a.must_equal [@o]
69
+
70
+ @c.get_foo(5, 6).must_equal 1
71
+
72
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4", "SELECT * FROM items WHERE (bar = 5) ORDER BY 6 LIMIT 1"]
73
+ end
74
+
75
+ it "should support :name option" do
76
+ @c.finder :foo, :name=>:find_foo
77
+ @c.find_foo(1, 2).must_equal @o
78
+ @c.find_foo(3, 4).must_equal @o
79
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
80
+ end
81
+
82
+ it "should support :arity option" do
83
+ def @c.foobar(*b)
84
+ ds = dataset
85
+ b.each_with_index do |a, i|
86
+ ds = ds.where(i=>a)
87
+ end
88
+ ds
89
+ end
90
+ @c.finder :foobar, :arity=>1, :name=>:find_foobar_1
91
+ @c.finder :foobar, :arity=>2, :name=>:find_foobar_2
92
+ @c.find_foobar_1(:a)
93
+ @c.find_foobar_2(:a, :b)
94
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (0 = a) LIMIT 1", "SELECT * FROM items WHERE ((0 = a) AND (1 = b)) LIMIT 1"]
95
+ end
96
+
97
+ it "should support :mod option" do
98
+ m = Module.new
99
+ @c.finder :foo, :mod=>m
100
+ proc{@c.first_foo}.must_raise NoMethodError
101
+ @c.extend m
102
+ @c.first_foo(1, 2).must_equal @o
103
+ @c.first_foo(3, 4).must_equal @o
104
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1"]
105
+ end
106
+
107
+ it "should raise error when calling with the wrong arity" do
108
+ @c.finder :foo
109
+ proc{@c.first_foo(1)}.must_raise Sequel::Error
110
+ proc{@c.first_foo(1,2,3)}.must_raise Sequel::Error
111
+ end
112
+ end
113
+
114
+ describe Sequel::Model, ".prepared_finder" do
115
+ before do
116
+ @h = {:id=>1}
117
+ @db = Sequel.mock(:fetch=>@h)
118
+ @db.extend_datasets do
119
+ def select_sql
120
+ sql = super
121
+ sql << ' -- prepared' if is_a?(Sequel::Dataset::PreparedStatementMethods)
122
+ sql
123
+ end
124
+ end
125
+ @c = Class.new(Sequel::Model(@db[:items]))
126
+ @c.instance_eval do
127
+ def foo(a, b)
128
+ where(:bar=>a).order(b)
129
+ end
130
+ end
131
+ @c.plugin :finder
132
+ @o = @c.load(@h)
133
+ @db.sqls
134
+ end
135
+
136
+ it "should create a method that calls the method given and returns the first instance" do
137
+ @c.prepared_finder :foo
138
+ @c.first_foo(1, 2).must_equal @o
139
+ @c.first_foo(3, 4).must_equal @o
140
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1 -- prepared"]
141
+ end
142
+
143
+ it "should work correctly when subclassing" do
144
+ @c.prepared_finder(:foo)
145
+ @sc = Class.new(@c)
146
+ @sc.set_dataset :foos
147
+ @db.sqls
148
+ @sc.first_foo(1, 2).must_equal @sc.load(@h)
149
+ @sc.first_foo(3, 4).must_equal @sc.load(@h)
150
+ @db.sqls.must_equal ["SELECT * FROM foos WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1 -- prepared"]
151
+ end
152
+
153
+ it "should work correctly when dataset is modified" do
154
+ @c.prepared_finder(:foo)
155
+ @c.first_foo(1, 2).must_equal @o
156
+ @c.set_dataset :foos
157
+ @c.first_foo(3, 4).must_equal @o
158
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared", "SELECT * FROM foos LIMIT 1", "SELECT * FROM foos WHERE (bar = 3) ORDER BY 4 LIMIT 1 -- prepared"]
159
+ end
160
+
161
+ it "should create a method based on the given block if no method symbol provided" do
162
+ @c.prepared_finder(:name=>:first_foo){|a1| where(:id=>a1).limit(1)}
163
+ @c.first_foo(1).must_equal @o
164
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (id = 1) LIMIT 1 -- prepared"]
165
+ end
166
+
167
+ it "should raise an error if both a block and method symbol given" do
168
+ proc{@c.prepared_finder(:foo, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.must_raise(Sequel::Error)
169
+ end
170
+
171
+ it "should raise an error if two option hashes are provided" do
172
+ proc{@c.prepared_finder({:name2=>:foo}, :name=>:first_foo){|pl, ds| ds.where(pl.arg)}}.must_raise(Sequel::Error)
173
+ end
174
+
175
+ it "should support :type option" do
176
+ @c.prepared_finder :foo, :type=>:all
177
+ @c.prepared_finder :foo, :type=>:each
178
+
179
+ a = []
180
+ @c.all_foo(1, 2){|r| a << r}.must_equal [@o]
181
+ a.must_equal [@o]
182
+
183
+ a = []
184
+ @c.each_foo(3, 4){|r| a << r}
185
+ a.must_equal [@o]
186
+
187
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 -- prepared", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 -- prepared"]
188
+ end
189
+
190
+ it "should support :name option" do
191
+ @c.prepared_finder :foo, :name=>:find_foo
192
+ @c.find_foo(1, 2).must_equal @o
193
+ @c.find_foo(3, 4).must_equal @o
194
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1 -- prepared"]
195
+ end
196
+
197
+ it "should support :arity option" do
198
+ def @c.foobar(*b)
199
+ ds = dataset
200
+ b.each_with_index do |a, i|
201
+ ds = ds.where(i=>a)
202
+ end
203
+ ds
204
+ end
205
+ @c.prepared_finder :foobar, :arity=>1, :name=>:find_foobar_1
206
+ @c.prepared_finder :foobar, :arity=>2, :name=>:find_foobar_2
207
+ @c.find_foobar_1(:a)
208
+ @c.find_foobar_2(:a, :b)
209
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (0 = a) LIMIT 1 -- prepared", "SELECT * FROM items WHERE ((0 = a) AND (1 = b)) LIMIT 1 -- prepared"]
210
+ end
211
+
212
+ it "should support :mod option" do
213
+ m = Module.new
214
+ @c.prepared_finder :foo, :mod=>m
215
+ proc{@c.first_foo}.must_raise NoMethodError
216
+ @c.extend m
217
+ @c.first_foo(1, 2).must_equal @o
218
+ @c.first_foo(3, 4).must_equal @o
219
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared", "SELECT * FROM items WHERE (bar = 3) ORDER BY 4 LIMIT 1 -- prepared"]
220
+ end
221
+
222
+ it "should handle models with names" do
223
+ def @c.name; 'foobar' end
224
+ @c.prepared_finder :foo
225
+ @c.first_foo(1, 2).must_equal @o
226
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (bar = 1) ORDER BY 2 LIMIT 1 -- prepared"]
227
+ end
228
+ end
229
+
230
+ describe "Sequel::Model.freeze" do
231
+ it "should freeze the model class and not allow any changes" do
232
+ model = Class.new(Sequel::Model(:items))
233
+ model.plugin :finder
234
+ model.finder(:name=>:f_by_name){|pl, ds| ds.where(:name=>pl.arg).limit(1)}
235
+ model.freeze
236
+ model.f_by_name('a').must_equal model.call(:id=>1, :x=>1)
237
+ proc{model.finder(:name=>:first_by_name){|pl, ds| ds.where(:name=>pl.arg).limit(1)}}.must_raise RuntimeError, TypeError
238
+ end
239
+
240
+ it "should freeze a model class without a dataset without breaking" do
241
+ model = Class.new(Sequel::Model)
242
+ model.plugin :finder
243
+ model.freeze
244
+ proc{model.finder(:name=>:first_by_name){|pl, ds| ds.where(:name=>pl.arg).limit(1)}}.must_raise RuntimeError, TypeError
245
+ end
246
+
247
+ it "should allow subclasses of frozen model classes to work correctly" do
248
+ model = Class.new(Sequel::Model(:items))
249
+ model.plugin :finder
250
+ model.freeze
251
+ model = Class.new(model)
252
+ model.dataset = :items2
253
+
254
+ model.dataset_module{}
255
+ model.plugin Module.new
256
+ model.finder(:name=>:first_by_name){|pl, ds| ds.where(:name=>pl.arg).limit(1)}
257
+ model.first_by_name('a').values.must_equal(:id=>1, :x=>1)
258
+ end
259
+ end
260
+