Class: AppQuery::Q
- Inherits:
-
Object
- Object
- AppQuery::Q
- Defined in:
- lib/app_query.rb
Overview
Query object for building, rendering, and executing SQL queries.
Q wraps a SQL string (optionally with ERB templating) and provides methods for query execution, CTE manipulation, and result handling.
Method Groups
- Rendering — Process ERB templates to produce executable SQL.
- Query Execution — Execute queries against the database. These methods
wrap the equivalent
ActiveRecord::Base.connectionmethods (select_all,insert,update,delete). - Query Introspection — Inspect and analyze the structure of the query.
- Query Transformation — Create modified copies of the query. All transformation methods are immutable—they return a new Q instance and leave the original unchanged.
- CTE Manipulation — Add, replace, or reorder Common Table Expressions (CTEs). Like transformation methods, these return a new Q instance.
Instance Attribute Summary collapse
- #binds ⇒ String, ... readonly
- #cast ⇒ String, ... readonly
-
#cte_depth ⇒ Object
readonly
Returns the value of attribute cte_depth.
- #filename ⇒ String, ... readonly
- #name ⇒ String, ... readonly
- #sql ⇒ String, ... readonly
Rendering collapse
-
#render(vars = {}) ⇒ Q
Renders the ERB template with the given variables.
Query Execution collapse
-
#any?(s = nil, binds: {}) ⇒ Boolean
Returns whether any rows exist in the query result.
-
#column(c, s = nil, binds: {}, unique: false) ⇒ Array
Returns an array of values for a single column.
-
#column_names(s = nil, binds: {}) ⇒ Array<String>
Returns the column names from the query without fetching any rows.
-
#copy_to(s = nil, format: :csv, header: true, delimiter: nil, dest: nil, binds: {}) ⇒ String, ...
Executes COPY TO STDOUT for efficient data export.
-
#count(s = nil, binds: {}) ⇒ Integer
Returns the count of rows from the query.
-
#delete(binds: {}) ⇒ Integer
Executes a DELETE query.
-
#entries ⇒ Array<Hash>
Executes the query and returns results as an Array of Hashes.
-
#ids(s = nil, binds: {}) ⇒ Array
Returns an array of id values from the query.
-
#insert(binds: {}, returning: nil) ⇒ Integer, Object
Executes an INSERT query.
-
#last(s = nil, binds: {}, cast: self.cast) ⇒ Hash?
Executes the query and returns the last row.
-
#none?(s = nil, binds: {}) ⇒ Boolean
Returns whether no rows exist in the query result.
-
#select_all(s = nil, binds: {}, cast: self.cast) ⇒ Result
Executes the query and returns all matching rows.
-
#select_one(s = nil, binds: {}, cast: self.cast) ⇒ Hash?
(also: #first)
Executes the query and returns the first row.
-
#select_value(s = nil, binds: {}, cast: self.cast) ⇒ Object?
Executes the query and returns the first value of the first row.
-
#take(n, s = nil, binds: {}, cast: self.cast) ⇒ Array<Hash>
(also: #limit)
Executes the query and returns the first n rows.
-
#take_last(n, s = nil, binds: {}, cast: self.cast) ⇒ Array<Hash>
Executes the query and returns the last n rows.
-
#update(binds: {}) ⇒ Integer
Executes an UPDATE query.
Query Introspection collapse
-
#cte_names ⇒ Array<String>
Returns the names of all CTEs (Common Table Expressions) in the query.
-
#recursive? ⇒ Boolean
Checks if the query uses RECURSIVE CTEs.
-
#select ⇒ String?
Returns the SELECT clause of the query.
-
#tokenizer ⇒ Tokenizer
Returns the tokenizer instance for this query.
-
#tokens ⇒ Array<Hash>
Returns the tokenized representation of the SQL.
Query Transformation collapse
-
#add_binds(**binds) ⇒ Q
Returns a new query with binds added.
-
#with_binds(**binds) ⇒ Q
(also: #replace_binds)
Returns a new query with different bind parameters.
-
#with_cast(cast) ⇒ Q
Returns a new query with different cast settings.
-
#with_select(sql) ⇒ Q
Returns a new query with a modified SELECT statement.
-
#with_sql(sql) ⇒ Q
Returns a new query with different SQL.
CTE Manipulation collapse
-
#append_cte(cte) ⇒ Q
Appends a CTE to the end of the WITH clause.
-
#cte(name) ⇒ Q
Returns a new query focused on the specified CTE.
-
#prepend_cte(cte) ⇒ Q
Prepends a CTE to the beginning of the WITH clause.
-
#replace_cte(cte) ⇒ Q
Replaces an existing CTE with a new definition.
Instance Method Summary collapse
- #deep_dup(sql: self.sql, name: self.name, filename: self.filename, binds: self.binds.dup, cast: self.cast, cte_depth: self.cte_depth) ⇒ Object
-
#initialize(sql, name: nil, filename: nil, binds: {}, cast: true, cte_depth: 0) ⇒ Q
constructor
Creates a new query object.
- #to_arel ⇒ Object
-
#to_s ⇒ String
Returns the SQL string.
Constructor Details
#initialize(sql, name: nil, filename: nil, binds: {}, cast: true, cte_depth: 0) ⇒ Q
Creates a new query object.
333 334 335 336 337 338 339 340 341 |
# File 'lib/app_query.rb', line 333 def initialize(sql, name: nil, filename: nil, binds: {}, cast: true, cte_depth: 0) @sql = sql @name = name @filename = filename @binds = binds @cast = cast @cte_depth = cte_depth @binds = binds_with_defaults(sql, binds) end |
Instance Attribute Details
#binds ⇒ String, ... (readonly)
318 319 320 |
# File 'lib/app_query.rb', line 318 def binds @binds end |
#cast ⇒ String, ... (readonly)
318 319 320 |
# File 'lib/app_query.rb', line 318 def cast @cast end |
#cte_depth ⇒ Object (readonly)
Returns the value of attribute cte_depth.
343 344 345 |
# File 'lib/app_query.rb', line 343 def cte_depth @cte_depth end |
#filename ⇒ String, ... (readonly)
318 319 320 |
# File 'lib/app_query.rb', line 318 def filename @filename end |
#name ⇒ String, ... (readonly)
318 319 320 |
# File 'lib/app_query.rb', line 318 def name @name end |
#sql ⇒ String, ... (readonly)
318 319 320 |
# File 'lib/app_query.rb', line 318 def sql @sql end |
Instance Method Details
#add_binds(**binds) ⇒ Q
Returns a new query with binds added.
970 971 972 |
# File 'lib/app_query.rb', line 970 def add_binds(**binds) deep_dup(binds: self.binds.merge(binds)) end |
#any?(s = nil, binds: {}) ⇒ Boolean
Returns whether any rows exist in the query result.
Uses EXISTS which stops at the first matching row, making it more
efficient than count > 0 for large result sets.
613 614 615 |
# File 'lib/app_query.rb', line 613 def any?(s = nil, binds: {}) with_select(s).select_all("SELECT EXISTS(SELECT 1 FROM :_) e", binds:).column("e").first end |
#append_cte(cte) ⇒ Q
Appends a CTE to the end of the WITH clause.
If the query has no CTEs, wraps it with WITH. If the query already has CTEs, adds the new CTE at the end.
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 |
# File 'lib/app_query.rb', line 1138 def append_cte(cte) # early raise when cte is not valid sql add_recursive, to_append = Tokenizer.tokenize(cte, state: :lex_append_cte).then do |tokens| [!recursive? && tokens.find { _1[:t] == "RECURSIVE" }, tokens.reject { _1[:t] == "RECURSIVE" }] end if cte_names.none? with_sql("WITH #{cte}\n#{self}") else nof_ctes = cte_names.size with_sql(tokens.map do |token| nof_ctes -= 1 if token[:t] == "CTE_SELECT" if nof_ctes.zero? nof_ctes -= 1 token[:v] + to_append.map { _1[:v] }.join elsif token[:t] == "WITH" && add_recursive token[:v] + add_recursive[:v] else token[:v] end end.join) end end |
#column(c, s = nil, binds: {}, unique: false) ⇒ Array
Returns an array of values for a single column.
Wraps the query in a CTE and selects only the specified column, which is
more efficient than fetching all columns via select_all.column(name).
The column name is safely quoted, making this method safe for user input.
655 656 657 658 659 |
# File 'lib/app_query.rb', line 655 def column(c, s = nil, binds: {}, unique: false) quoted = quote_column(c) select_expr = unique ? "DISTINCT #{quoted}" : quoted with_select(s).select_all("SELECT #{select_expr} AS column FROM :_", binds:).column("column") end |
#column_names(s = nil, binds: {}) ⇒ Array<String>
Returns the column names from the query without fetching any rows.
Uses LIMIT 0 to get column metadata efficiently.
676 677 678 |
# File 'lib/app_query.rb', line 676 def column_names(s = nil, binds: {}) with_select(s).select_all("SELECT * FROM :_ LIMIT 0", binds:).columns end |
#copy_to(s = nil, format: :csv, header: true, delimiter: nil, dest: nil, binds: {}) ⇒ String, ...
Executes COPY TO STDOUT for efficient data export.
PostgreSQL-only. Uses raw connection for streaming. Raises an error when used with SQLite or other non-PostgreSQL adapters.
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 |
# File 'lib/app_query.rb', line 854 def copy_to(s = nil, format: :csv, header: true, delimiter: nil, dest: nil, binds: {}) raw_conn = ActiveRecord::Base.connection.raw_connection unless raw_conn.respond_to?(:copy_data) raise Error, "copy_to requires PostgreSQL (current adapter does not support COPY)" end allowed_formats = %i[csv text binary] unless allowed_formats.include?(format) raise ArgumentError, "Invalid format: #{format.inspect}. Allowed: #{allowed_formats.join(", ")}" end delimiters = {tab: "\t", comma: ",", pipe: "|", semicolon: ";"} if delimiter if !delimiters.key?(delimiter) raise ArgumentError, "Invalid delimiter: #{delimiter.inspect}. Allowed: #{delimiters.keys.join(", ")}" elsif format == :binary raise ArgumentError, "Delimiter not allowed for format :binary" end end add_binds(**binds).with_select(s).render({}).then do |aq| = ["FORMAT #{format.to_s.upcase}"] << "HEADER" if header && format == :csv << "DELIMITER E'#{delimiters[delimiter]}'" if delimiter inner_sql = ActiveRecord::Base.sanitize_sql_array([aq.to_s, aq.binds]) copy_sql = "COPY (#{inner_sql}) TO STDOUT WITH (#{.join(", ")})" case dest when NilClass output = +"" raw_conn.copy_data(copy_sql) do while (row = raw_conn.get_copy_data) output << row end end # pg returns ASCII-8BIT, but CSV/text is UTF-8; binary stays as-is (format == :binary) ? output : output.force_encoding(Encoding::UTF_8) when String bytes = 0 File.open(dest, "wb") do |f| raw_conn.copy_data(copy_sql) do while (row = raw_conn.get_copy_data) bytes += f.write(row) end end end bytes else raw_conn.copy_data(copy_sql) do while (row = raw_conn.get_copy_data) dest.write(row) end end nil end end end |
#count(s = nil, binds: {}) ⇒ Integer
Returns the count of rows from the query.
Wraps the query in a CTE and selects only the count, which is more
efficient than fetching all rows via select_all.count.
593 594 595 |
# File 'lib/app_query.rb', line 593 def count(s = nil, binds: {}) with_select(s).select_all("SELECT COUNT(*) c FROM :_", binds:).column("c").first end |
#cte(name) ⇒ Q
Returns a new query focused on the specified CTE.
Wraps the query to select from the named CTE, allowing you to inspect or test individual CTEs in isolation.
1078 1079 1080 1081 1082 1083 1084 |
# File 'lib/app_query.rb', line 1078 def cte(name) name = name.to_s unless cte_names.include?(name) raise ArgumentError, "Unknown CTE #{name.inspect}. Available: #{cte_names.inspect}" end with_select("SELECT * FROM #{quote_table(name)}") end |
#cte_names ⇒ Array<String>
Returns the names of all CTEs (Common Table Expressions) in the query.
941 942 943 |
# File 'lib/app_query.rb', line 941 def cte_names tokens.filter { _1[:t] == "CTE_IDENTIFIER" }.map { _1[:v].delete_prefix('"').delete_suffix('"') } end |
#deep_dup(sql: self.sql, name: self.name, filename: self.filename, binds: self.binds.dup, cast: self.cast, cte_depth: self.cte_depth) ⇒ Object
362 363 364 |
# File 'lib/app_query.rb', line 362 def deep_dup(sql: self.sql, name: self.name, filename: self.filename, binds: self.binds.dup, cast: self.cast, cte_depth: self.cte_depth) self.class.new(sql, name:, filename:, binds:, cast:, cte_depth:) end |
#delete(binds: {}) ⇒ Integer
Executes a DELETE query.
798 799 800 801 802 803 804 805 806 807 808 809 810 |
# File 'lib/app_query.rb', line 798 def delete(binds: {}) with_binds(**binds).render({}).then do |aq| sql = if ActiveRecord::VERSION::STRING.to_f >= 7.1 aq.to_arel else ActiveRecord::Base.sanitize_sql_array([aq.to_s, **aq.binds]) end ActiveRecord::Base.connection.delete(sql, name) end rescue NameError => e raise e unless e.instance_of?(NameError) raise UnrenderedQueryError, "Query is ERB. Use #render before deleting." end |
#entries ⇒ Array<Hash>
Executes the query and returns results as an Array of Hashes.
Shorthand for select_all(...).entries. Accepts the same arguments as
#select_all.
712 713 714 |
# File 'lib/app_query.rb', line 712 def entries(...) select_all(...).entries end |
#ids(s = nil, binds: {}) ⇒ Array
Returns an array of id values from the query.
Convenience method equivalent to column(:id). More efficient than
fetching all columns via select_all.column("id").
696 697 698 |
# File 'lib/app_query.rb', line 696 def ids(s = nil, binds: {}) column(:id, s, binds:) end |
#insert(binds: {}, returning: nil) ⇒ Integer, Object
Executes an INSERT query.
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 |
# File 'lib/app_query.rb', line 734 def insert(binds: {}, returning: nil) # ActiveRecord::Base.connection.insert(sql, name, _pk = nil, _id_value = nil, _sequence_name = nil, binds, returning: nil) if returning && ActiveRecord::VERSION::STRING.to_f < 7.1 raise ArgumentError, "The 'returning' option requires Rails 7.1+. Current version: #{ActiveRecord::VERSION::STRING}" end with_binds(**binds).render({}).then do |aq| sql = if ActiveRecord::VERSION::STRING.to_f >= 7.1 aq.to_arel else ActiveRecord::Base.sanitize_sql_array([aq.to_s, **aq.binds]) end if ActiveRecord::VERSION::STRING.to_f >= 7.1 ActiveRecord::Base.connection.insert(sql, name, returning:) else ActiveRecord::Base.connection.insert(sql, name) end end rescue NameError => e # Prevent any subclasses, e.g. NoMethodError raise e unless e.instance_of?(NameError) raise UnrenderedQueryError, "Query is ERB. Use #render before select-ing." end |
#last(s = nil, binds: {}, cast: self.cast) ⇒ Hash?
Executes the query and returns the last row.
Uses OFFSET to skip to the last row without changing the query order. Note: This requires counting all rows first, so it's less efficient than #first for large result sets.
513 514 515 |
# File 'lib/app_query.rb', line 513 def last(s = nil, binds: {}, cast: self.cast) take_last(1, s, binds:, cast:).first end |
#none?(s = nil, binds: {}) ⇒ Boolean
Returns whether no rows exist in the query result.
Inverse of #any?. Uses EXISTS for efficiency.
628 629 630 |
# File 'lib/app_query.rb', line 628 def none?(s = nil, binds: {}) !any?(s, binds:) end |
#prepend_cte(cte) ⇒ Q
Prepends a CTE to the beginning of the WITH clause.
If the query has no CTEs, wraps it with WITH. If the query already has CTEs, adds the new CTE at the beginning.
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 |
# File 'lib/app_query.rb', line 1102 def prepend_cte(cte) # early raise when cte is not valid sql to_append = Tokenizer.tokenize(cte, state: :lex_prepend_cte).then do |tokens| recursive? ? tokens.reject { _1[:t] == "RECURSIVE" } : tokens end if cte_names.none? with_sql("WITH #{cte}\n#{self}") else split_at_type = recursive? ? "RECURSIVE" : "WITH" with_sql(tokens.map do |token| if token[:t] == split_at_type token[:v] + to_append.map { _1[:v] }.join else token[:v] end end.join) end end |
#recursive? ⇒ Boolean
Checks if the query uses RECURSIVE CTEs.
1057 1058 1059 |
# File 'lib/app_query.rb', line 1057 def recursive? !!tokens.find { _1[:t] == "RECURSIVE" } end |
#render(vars = {}) ⇒ Q
Renders the ERB template with the given variables.
Processes ERB tags in the SQL and collects any bind parameters created by helpers like RenderHelpers#bind and RenderHelpers#values.
403 404 405 406 407 408 409 410 |
# File 'lib/app_query.rb', line 403 def render(vars = {}) vars ||= {} helper = render_helper(vars) sql = to_erb.result(helper.get_binding) collected = helper.collected_binds with_sql(sql).add_binds(**collected) end |
#replace_cte(cte) ⇒ Q
Replaces an existing CTE with a new definition.
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 |
# File 'lib/app_query.rb', line 1176 def replace_cte(cte) add_recursive, to_append = Tokenizer.tokenize(cte, state: :lex_recursive_cte).then do |tokens| [!recursive? && tokens.find { _1[:t] == "RECURSIVE" }, tokens.reject { _1[:t] == "RECURSIVE" }] end cte_name = to_append.find { _1[:t] == "CTE_IDENTIFIER" }&.[](:v) unless cte_names.include?(cte_name) raise ArgumentError, "Unknown cte #{cte_name.inspect}. Options: #{cte_names}." end cte_ix = cte_names.index(cte_name) return self unless cte_ix cte_found = false with_sql(tokens.map do |token| if cte_found ||= token[:t] == "CTE_IDENTIFIER" && token[:v] == cte_name unless (cte_found = (token[:t] != "CTE_SELECT")) next to_append.map { _1[:v] }.join end next elsif token[:t] == "WITH" && add_recursive token[:v] + add_recursive[:v] else token[:v] end end.join) end |
#select ⇒ String?
Returns the SELECT clause of the query.
1046 1047 1048 |
# File 'lib/app_query.rb', line 1046 def select tokens.find { _1[:t] == "SELECT" }&.[](:v) end |
#select_all(s = nil, binds: {}, cast: self.cast) ⇒ Result
Executes the query and returns all matching rows.
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 |
# File 'lib/app_query.rb', line 463 def select_all(s = nil, binds: {}, cast: self.cast) add_binds(**binds).with_select(s).render({}).then do |aq| sql = if ActiveRecord::VERSION::STRING.to_f >= 7.1 aq.to_arel else ActiveRecord::Base.sanitize_sql_array([aq.to_s, aq.binds]) end ActiveRecord::Base.connection.select_all(sql, aq.name).then do |result| Result.from_ar_result(result, cast) end end rescue NameError => e # Prevent any subclasses, e.g. NoMethodError raise e unless e.instance_of?(NameError) raise UnrenderedQueryError, "Query is ERB. Use #render before select-ing." end |
#select_one(s = nil, binds: {}, cast: self.cast) ⇒ Hash? Also known as: first
Executes the query and returns the first row.
492 493 494 |
# File 'lib/app_query.rb', line 492 def select_one(s = nil, binds: {}, cast: self.cast) with_select(s).select_all("SELECT * FROM :_ LIMIT 1", binds:, cast:).first end |
#select_value(s = nil, binds: {}, cast: self.cast) ⇒ Object?
Executes the query and returns the first value of the first row.
571 572 573 |
# File 'lib/app_query.rb', line 571 def select_value(s = nil, binds: {}, cast: self.cast) select_one(s, binds:, cast:)&.values&.first end |
#take(n, s = nil, binds: {}, cast: self.cast) ⇒ Array<Hash> Also known as: limit
Executes the query and returns the first n rows.
530 531 532 |
# File 'lib/app_query.rb', line 530 def take(n, s = nil, binds: {}, cast: self.cast) with_select(s).select_all("SELECT * FROM :_ LIMIT #{n.to_i}", binds:, cast:).entries end |
#take_last(n, s = nil, binds: {}, cast: self.cast) ⇒ Array<Hash>
Executes the query and returns the last n rows.
Uses OFFSET to skip to the last n rows without changing the query order. Note: This requires counting all rows first, so it's less efficient than #take for large result sets.
552 553 554 555 556 557 |
# File 'lib/app_query.rb', line 552 def take_last(n, s = nil, binds: {}, cast: self.cast) with_select(s).select_all( "SELECT * FROM :_ LIMIT #{n.to_i} OFFSET GREATEST((SELECT COUNT(*) FROM :_) - #{n.to_i}, 0)", binds:, cast: ).entries end |
#to_arel ⇒ Object
345 346 347 348 349 350 351 352 |
# File 'lib/app_query.rb', line 345 def to_arel if binds.presence Arel::Nodes::BoundSqlLiteral.new sql, [], binds else # TODO: add retryable? available from >=7.1 Arel::Nodes::SqlLiteral.new(sql) end end |
#to_s ⇒ String
Returns the SQL string.
1212 1213 1214 |
# File 'lib/app_query.rb', line 1212 def to_s @sql end |
#tokenizer ⇒ Tokenizer
Returns the tokenizer instance for this query.
926 927 928 |
# File 'lib/app_query.rb', line 926 def tokenizer @tokenizer ||= Tokenizer.new(to_s) end |
#tokens ⇒ Array<Hash>
Returns the tokenized representation of the SQL.
919 920 921 |
# File 'lib/app_query.rb', line 919 def tokens @tokens ||= tokenizer.run end |
#update(binds: {}) ⇒ Integer
Executes an UPDATE query.
772 773 774 775 776 777 778 779 780 781 782 783 784 |
# File 'lib/app_query.rb', line 772 def update(binds: {}) with_binds(**binds).render({}).then do |aq| sql = if ActiveRecord::VERSION::STRING.to_f >= 7.1 aq.to_arel else ActiveRecord::Base.sanitize_sql_array([aq.to_s, **aq.binds]) end ActiveRecord::Base.connection.update(sql, name) end rescue NameError => e raise e unless e.instance_of?(NameError) raise UnrenderedQueryError, "Query is ERB. Use #render before updating." end |
#with_binds(**binds) ⇒ Q Also known as: replace_binds
Returns a new query with different bind parameters.
956 957 958 |
# File 'lib/app_query.rb', line 956 def with_binds(**binds) deep_dup(binds:) end |
#with_cast(cast) ⇒ Q
Returns a new query with different cast settings.
982 983 984 |
# File 'lib/app_query.rb', line 982 def with_cast(cast) deep_dup(cast:) end |
#with_select(sql) ⇒ Q
Returns a new query with a modified SELECT statement.
Wraps the current SELECT in a numbered CTE and applies the new SELECT.
CTEs are named _, _1, _2, etc. Use :_ in the new SELECT to
reference the previous result.
1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 |
# File 'lib/app_query.rb', line 1014 def with_select(sql) return self if sql.nil? # First CTE is "_", then "_1", "_2", etc. current_cte = (cte_depth == 0) ? "_" : "_#{cte_depth}" # Replace :_ with the current CTE name processed_sql = sql.gsub(/:_\b/, current_cte) # Wrap current SELECT in numbered CTE (indent all lines, strip trailing whitespace) indented_select = select.rstrip.gsub("\n", "\n ") new_cte = "#{current_cte} AS (\n #{indented_select}\n)" append_cte(new_cte).then do |q| # Replace the SELECT token with processed_sql and increment depth new_sql = q.tokens.each_with_object([]) do |token, acc| v = (token[:t] == "SELECT") ? processed_sql : token[:v] acc << v end.join q.deep_dup(sql: new_sql, cte_depth: cte_depth + 1) end end |
#with_sql(sql) ⇒ Q
Returns a new query with different SQL.
990 991 992 |
# File 'lib/app_query.rb', line 990 def with_sql(sql) deep_dup(sql:) end |