sequel 4.45.0 → 4.46.0

Sign up to get free protection for your applications and to get access to all the features.
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
+