From 45b7f370709c476bc4b7e397fceaa8be0fcb6f99 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Thu, 5 Sep 2024 15:32:52 -0700 Subject: [PATCH] Make Dataset#get and #first without argument not create intermediate datasets if receiver uses raw SQL There's no reason for a dataset clone in this case, as the limit and select settings will be ignored. This also fixes use of Dataset#get without argument when using the implicit_subquery extension. Previously, it would try to use a subquery in an unhelpful way: DB['SELECT a FROM b WHERE c = 1'].extension(:implicit_subquery).get # SELECT NULL AS v FROM (SELECT a FROM b WHERE c = 1) AS t1 LIMIT 1 Now, it runs the query directly and returns the first value. --- CHANGELOG | 2 ++ lib/sequel/dataset/actions.rb | 10 +++++++++- spec/core/dataset_spec.rb | 10 +++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d43cbdd97..523faff3e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ === master +* Make Dataset#get and #first without argument not create intermediate datasets if receiver uses raw SQL (jeremyevans) + * Add dataset_run extension, for building SQL using datasets, and running with Database#run (jeremyevans) * Switch default connection pool to timed_queue on Ruby 3.2+ (jeremyevans) diff --git a/lib/sequel/dataset/actions.rb b/lib/sequel/dataset/actions.rb index d8b2ef742..a9e4f80ed 100644 --- a/lib/sequel/dataset/actions.rb +++ b/lib/sequel/dataset/actions.rb @@ -217,7 +217,7 @@ def first(*args, &block) case args.length when 0 unless block - return single_record + return(@opts[:sql] ? single_record! : single_record) end when 1 arg = args[0] @@ -282,6 +282,12 @@ def first!(*args, &block) # # DB[:table].get{[sum(id).as(sum), name]} # SELECT sum(id) AS sum, name FROM table LIMIT 1 # # => [6, 'foo'] + # + # If called on a dataset with raw SQL, returns the + # first value in the dataset without changing the selection or setting a limit: + # + # DB["SELECT id FROM table"].get # SELECT id FROM table + # # => 3 def get(column=(no_arg=true; nil), &block) ds = naked if block @@ -289,6 +295,8 @@ def get(column=(no_arg=true; nil), &block) ds = ds.select(&block) column = ds.opts[:select] column = nil if column.is_a?(Array) && column.length < 2 + elsif no_arg && opts[:sql] + return ds.single_value! else case column when Array diff --git a/spec/core/dataset_spec.rb b/spec/core/dataset_spec.rb index 6804e9c77..c441e4c41 100644 --- a/spec/core/dataset_spec.rb +++ b/spec/core/dataset_spec.rb @@ -3035,6 +3035,12 @@ def supports_cte_in_subselect?; false end Sequel.mock[:t].first.must_be_nil end + it "should return first record in query when using raw SQL" do + db = Sequel.mock(:fetch=>{:v=>1}) + db['SELECT 1'].first.must_equal(:v=>1) + db.sqls.must_equal ['SELECT 1'] + end + it "#last should raise if no order is given" do proc {@d.last}.must_raise(Sequel::Error) proc {@d.last(2)}.must_raise(Sequel::Error) @@ -3375,7 +3381,9 @@ def supports_cte_in_subselect?; false end @d.with_sql('SELECT foo').get(:name).must_equal "SELECT foo" @d = @d.with_fetch(:name=>1, :abc=>2) @d.with_sql('SELECT foo').get{[name, n[abc]]}.must_equal [1, 2] - @d.db.sqls.must_equal ['SELECT foo'] * 2 + @d = @d.with_fetch(:name=>1) + @d.with_sql('SELECT foo').get.must_equal 1 + @d.db.sqls.must_equal ['SELECT foo'] * 3 end it "should handle cases where no rows are returned" do