# This module implements an adapter between the Simple::SQL interface # (i.e. ask, all, first, transaction) and a raw connection. # # This module can be mixed onto objects that implement a raw_connection # method, which must return a Pg::Connection. module Simple::SQL::ConnectionAdapter Logging = ::Simple::SQL::Logging # execute one or more sql statements. This method does not allow to pass in # arguments - since the pg client does not support this - but it allows to # run multiple sql statements separated by ";" def exec(sql) Logging.with_logged_query sql do raw_connection.exec sql end end # Runs a query, with optional arguments, and returns the result. If the SQL # query returns rows with one column, this method returns an array of these # values. Otherwise it returns an array of arrays. # # Example: # # - Simple::SQL.all("SELECT id FROM users") returns an array of id values # - Simple::SQL.all("SELECT id, email FROM users") returns an array of # arrays `[ , ]`. # # Simple::SQL.all "SELECT id, email FROM users" do |id, email| # # do something # end def all(sql, *args, into: nil, &block) raise ArgumentError, "all no longer support blocks, use each instead." if block rows = [] my_pg_source_oid = nil each_without_conversion(sql, *args, into: into) do |row, pg_source_oid| rows << row my_pg_source_oid = pg_source_oid end record_set = convert_rows_to_result rows, into: into, pg_source_oid: my_pg_source_oid # [TODO] - resolve associations. Note that this is only possible if the type # is not an Array (i.e. into is nil) if sql.is_a?(::Simple::SQL::Connection::Scope) && sql.paginated? record_set.send(:set_pagination_info, sql) end record_set end def each(sql, *args, into: nil) raise ArgumentError, "Missing block" unless block_given? each_without_conversion sql, *args, into: into do |row, pg_source_oid| record = convert_row_to_record row, into: into, pg_source_oid: pg_source_oid yield record end self end # Runs a query and prints the results via "table_print" def print(sql, *args, into: nil) raise ArgumentError, "You cannot call Simple::SQL.print with into: #{into.inspect}" unless into.nil? require "table_print" records = all sql, *args, into: Hash tp records records end # Runs a query and returns the first result row of a query. # # Examples: # # - Simple::SQL.ask "SELECT id FROM users WHERE email=$?", "foo@local" # returns a number (or +nil+) # - Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local" # returns an array [ , ] (or +nil+) def ask(sql, *args, into: nil) catch(:ok) do each(sql, *args, into: into) { |row| throw :ok, row } nil end end # returns an Array [min_cost, max_cost] based on the database's estimation def costs(sql, *args) explanation_first = Simple::SQL.ask "EXPLAIN #{sql}", *args unless explanation_first =~ /cost=(\d+(\.\d+))\.+(\d+(\.\d+))/ raise "Cannot determine cost" end [Float($1), Float($3)] end # Executes a block, usually of db insert code, while holding an # advisory lock. # # Examples: # # - Simple::SQL.locked(4711) { puts 'do work while locked' } def locked(lock_id) ask("SELECT pg_advisory_lock(#{lock_id})") yield ensure ask("SELECT pg_advisory_unlock(#{lock_id})") end private Result = ::Simple::SQL::Result Decoder = ::Simple::SQL::Helpers::Decoder Encoder = ::Simple::SQL::Helpers::Encoder def exec_logged(sql_or_scope, *args) if sql_or_scope.is_a?(::Simple::SQL::Connection::Scope) raise ArgumentError, "You cannot call .all with a scope and additional arguments" unless args.empty? raise ArgumentError, "You cannot execute a scope on a different connection" unless self == sql_or_scope.connection sql = sql_or_scope.to_sql args = sql_or_scope.args else sql = sql_or_scope end Logging.with_logged_query sql, *args do raw_connection.exec_params(sql, Encoder.encode_args(raw_connection, args)) end end def each_without_conversion(sql, *args, into: nil) pg_result = exec_logged(sql, *args) if pg_result.ntuples > 0 && pg_result.nfields > 0 decoder = Decoder.new(self, pg_result, into: (into ? Hash : nil)) pg_source_oid = pg_result.ftable(0) pg_result.each_row do |row| yield decoder.decode(row), pg_source_oid end end # optimization: If we wouldn't clear here the GC would do this later. pg_result.clear unless pg_result.autoclear? end def convert_row_to_record(row, into:, pg_source_oid:) convert_rows_to_result([row], into: into, pg_source_oid: pg_source_oid).first end def convert_rows_to_result(rows, into:, pg_source_oid:) Result.build(self, rows, target_type: into, pg_source_oid: pg_source_oid) end public def resolve_type(ftype, fmod) @resolved_types ||= {} @resolved_types[[ftype, fmod]] ||= raw_connection.exec("SELECT format_type($1,$2)", [ftype, fmod]).getvalue(0, 0) end end