Skip to content

Commit

Permalink
Merge pull request #250 from GUI/backslash_escaping
Browse files Browse the repository at this point in the history
Fix PostgreSQL backslash escaping to account for standard_conforming_strings
  • Loading branch information
ajuckel committed Oct 23, 2012
2 parents 3cdbcf2 + b0878fc commit 0f0f7bd
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 12 deletions.
70 changes: 58 additions & 12 deletions lib/arjdbc/postgresql/adapter.rb
Expand Up @@ -8,6 +8,8 @@ def self.extended(mod)
(class << mod; self; end).class_eval do
alias_chained_method :columns, :query_cache, :pg_columns
end

mod.configure_connection
end

def self.column_selector
Expand All @@ -18,6 +20,10 @@ def self.jdbc_connection_class
::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
end

def configure_connection
standard_conforming_strings = true
end

# column behavior based on postgresql_adapter in rails project
# https://github.com/rails/rails/blob/3-1-stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L41
module Column
Expand Down Expand Up @@ -207,20 +213,39 @@ def supports_migrations?
true
end

# Enable standard-conforming strings if available.
def standard_conforming_strings=(enable)
old, self.client_min_messages = client_min_messages, 'panic'
value = if(enable) then "on" else "off" end
execute("SET standard_conforming_strings = #{value}", 'SCHEMA')
@standard_conforming_strings = (value == "on")
rescue
@standard_conforming_strings = :unsupported
ensure
self.client_min_messages = old
end

def standard_conforming_strings? #:nodoc:
if @standard_conforming_strings.nil?
begin
old, self.client_min_messages = client_min_messages, 'panic'
value = select_one('SHOW standard_conforming_strings', 'SCHEMA')['standard_conforming_strings']
@standard_conforming_strings = (value == "on")
rescue
@standard_conforming_strings = :unsupported
ensure
self.client_min_messages = old
end
end

# Return false if unsupported.
@standard_conforming_strings == true
end

# Does PostgreSQL support standard conforming strings?
def supports_standard_conforming_strings?
# Temporarily set the client message level above error to prevent unintentional
# error messages in the logs when working on a PostgreSQL database server that
# does not support standard conforming strings.
client_min_messages_old = client_min_messages
self.client_min_messages = 'panic'

# postgres-pr does not raise an exception when client_min_messages is set higher
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
# PGresult instead.
has_support = select('SHOW standard_conforming_strings').to_a[0][0] rescue false
self.client_min_messages = client_min_messages_old
has_support
standard_conforming_strings?
@standard_conforming_strings != :unsupported
end

def supports_hex_escaped_bytea?
Expand Down Expand Up @@ -546,6 +571,16 @@ def current_schema
exec_query('SELECT current_schema', 'SCHEMA')[0]["current_schema"]
end

# Returns the current client message level.
def client_min_messages
exec_query('SHOW client_min_messages', 'SCHEMA')[0]['client_min_messages']
end

# Set the client message level.
def client_min_messages=(level)
execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
end

# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
#
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
Expand Down Expand Up @@ -610,6 +645,17 @@ def quote(value, column = nil) #:nodoc:
end
end

# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
quoted = s.gsub(/'/, "''")
if !standard_conforming_strings?
quoted.gsub!(/\\/, '\&\&')
end

quoted
end

def escape_bytea(s)
if s
if supports_hex_escaped_bytea?
Expand Down
26 changes: 26 additions & 0 deletions test/postgres_simple_test.rb
Expand Up @@ -66,6 +66,32 @@ def test_create_table_with_limits
ensure
@connection.drop_table :testings rescue nil
end

def test_supports_standard_conforming_string
assert([true, false].include?(@connection.supports_standard_conforming_strings?))
end

def test_default_standard_conforming_string
if @connection.supports_standard_conforming_strings?
assert_equal true, @connection.standard_conforming_strings?
else
assert_equal false, @connection.standard_conforming_strings?
end
end

def test_string_quoting_with_standard_conforming_strings
if @connection.supports_standard_conforming_strings?
s = "\\m it's \\M"
assert_equal "'\\m it''s \\M'", @connection.quote(s)
end
end

def test_string_quoting_without_standard_conforming_strings
@connection.standard_conforming_strings = false
s = "\\m it's \\M"
assert_equal "'\\\\m it''s \\\\M'", @connection.quote(s)
@connection.standard_conforming_strings = true
end
end

class PostgresTimestampTest < Test::Unit::TestCase
Expand Down

0 comments on commit 0f0f7bd

Please sign in to comment.