提交 9889308b 编写于 作者: C chrismo

Allow CTEs in read-only queries.

Common Table Expressions in PostgreSQL allow a different way to define
derived tables. Here's an example from the pg docs:

	WITH regional_sales AS (
	        SELECT region, SUM(amount) AS total_sales
	        FROM orders
	        GROUP BY region
	     ), top_regions AS (
	        SELECT region
	        FROM regional_sales
	        WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
	     )
	SELECT region,
	       product,
	       SUM(quantity) AS product_units,
	       SUM(amount) AS product_sales
	FROM orders
	WHERE region IN (SELECT region FROM top_regions)
	GROUP BY region, product;

https://www.postgresql.org/docs/current/queries-with.html

This commit adds the :with keyword to the set of keywords allowed to
begin a query against a `prevent_writes` PostgreSQL connection.

Thx to @kamipo, this also adds the same support for sqlite3 and mysql2

https://www.sqlite.org/lang_with.html
https://dev.mysql.com/doc/refman/8.0/en/with.html
上级 d9dfb4fd
...@@ -20,7 +20,7 @@ def query(sql, name = nil) # :nodoc: ...@@ -20,7 +20,7 @@ def query(sql, name = nil) # :nodoc:
end end
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp( READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc, :with
) # :nodoc: ) # :nodoc:
private_constant :READ_QUERY private_constant :READ_QUERY
......
...@@ -67,7 +67,7 @@ def query(sql, name = nil) #:nodoc: ...@@ -67,7 +67,7 @@ def query(sql, name = nil) #:nodoc:
end end
end end
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc: READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :with) # :nodoc:
private_constant :READ_QUERY private_constant :READ_QUERY
def write_query?(sql) # :nodoc: def write_query?(sql) # :nodoc:
......
...@@ -4,7 +4,7 @@ module ActiveRecord ...@@ -4,7 +4,7 @@ module ActiveRecord
module ConnectionAdapters module ConnectionAdapters
module SQLite3 module SQLite3
module DatabaseStatements module DatabaseStatements
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc: READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with) # :nodoc:
private_constant :READ_QUERY private_constant :READ_QUERY
def write_query?(sql) # :nodoc: def write_query?(sql) # :nodoc:
......
...@@ -232,6 +232,15 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve ...@@ -232,6 +232,15 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve
end end
end end
def test_doesnt_error_when_a_read_query_with_a_cte_is_called_while_preventing_writes
@conn.execute("INSERT INTO `engines` (`car_id`) VALUES ('138853948594')")
@connection_handler.while_preventing_writes do
sql = "WITH matching_cars AS (SELECT `engines`.* FROM `engines` WHERE `engines`.`car_id` = '138853948594') SELECT * FROM matching_cars"
assert_equal 1, @conn.execute(sql).entries.count
end
end
def test_read_timeout_exception def test_read_timeout_exception
ActiveRecord::Base.establish_connection( ActiveRecord::Base.establish_connection(
ActiveRecord::Base.configurations[:arunit].merge("read_timeout" => 1) ActiveRecord::Base.configurations[:arunit].merge("read_timeout" => 1)
......
...@@ -457,6 +457,17 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve ...@@ -457,6 +457,17 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve
end end
end end
def test_doesnt_error_when_a_read_query_with_a_cte_is_called_while_preventing_writes
with_example_table do
@connection.execute("INSERT INTO ex (data) VALUES ('138853948594')")
@connection_handler.while_preventing_writes do
sql = "WITH matching_ex_values AS (SELECT * FROM ex WHERE data = '138853948594') SELECT * FROM matching_ex_values"
assert_equal 1, @connection.execute(sql).entries.count
end
end
end
private private
def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block) def with_example_table(definition = "id serial primary key, number integer, data character varying(255)", &block)
super(@connection, "ex", definition, &block) super(@connection, "ex", definition, &block)
......
...@@ -652,6 +652,17 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve ...@@ -652,6 +652,17 @@ def test_doesnt_error_when_a_read_query_with_leading_chars_is_called_while_preve
end end
end end
def test_doesnt_error_when_a_read_query_with_a_cte_is_called_while_preventing_writes
with_example_table "id int, data string" do
@conn.execute("INSERT INTO ex (data) VALUES ('138853948594')")
@connection_handler.while_preventing_writes do
sql = "WITH matching_ex_values AS (SELECT * FROM ex WHERE data = '138853948594') SELECT * FROM matching_ex_values"
assert_equal 1, @conn.execute(sql).entries.count
end
end
end
private private
def assert_logged(logs) def assert_logged(logs)
subscriber = SQLSubscriber.new subscriber = SQLSubscriber.new
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册