Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

PostgreSQL: XML datatype support

[#1874 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
  • Loading branch information...
commit 0c391b46fb39b697bbae1493caade23e2ddbd8a6 1 parent 9c1bac0
Leonardo Borges leonardoborges authored jeremy committed
2  activerecord/CHANGELOG
View
@@ -1,5 +1,7 @@
*Edge*
+* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
+
* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
15 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
View
@@ -315,6 +315,20 @@ def initialize(base)
@base = base
end
+ #Handles non supported datatypes - e.g. XML
+ def method_missing(symbol, *args)
+ if symbol.to_s == 'xml'
+ xml_column_fallback(args)
+ end
+ end
+
+ def xml_column_fallback(*args)
+ case @base.adapter_name.downcase
+ when 'sqlite', 'mysql'
+ options = args.extract_options!
+ column(args[0], :text, options)
+ end
+ end
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
def primary_key(name)
@@ -705,3 +719,4 @@ def native
end
end
+
34 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
View
@@ -40,6 +40,12 @@ def self.postgresql_connection(config) # :nodoc:
end
module ConnectionAdapters
+ class TableDefinition
+ def xml(*args)
+ options = args.extract_options!
+ column(args[0], 'xml', options)
+ end
+ end
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
@@ -68,7 +74,7 @@ def extract_precision(sql_type)
# depending on the server specifics
super
end
-
+
# Maps PostgreSQL-specific data types to logical Rails types.
def simplified_type(field_type)
case field_type
@@ -100,10 +106,10 @@ def simplified_type(field_type)
:string
# XML type
when /^xml$/
- :string
+ :xml
# Arrays
when /^\D+\[\]$/
- :string
+ :string
# Object identifier types
when /^oid$/
:integer
@@ -112,7 +118,7 @@ def simplified_type(field_type)
super
end
end
-
+
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
@@ -195,7 +201,8 @@ class PostgreSQLAdapter < AbstractAdapter
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "bytea" },
- :boolean => { :name => "boolean" }
+ :boolean => { :name => "boolean" },
+ :xml => { :name => "xml" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -278,7 +285,7 @@ def supports_insert_with_returning?
def supports_ddl_transactions?
true
end
-
+
def supports_savepoints?
true
end
@@ -370,7 +377,7 @@ def quote(value, column = nil) #:nodoc:
if value.kind_of?(String) && column && column.type == :binary
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
- "xml '#{quote_string(value)}'"
+ "xml E'#{quote_string(value)}'"
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value.to_s}'"
@@ -569,7 +576,7 @@ def commit_db_transaction
def rollback_db_transaction
execute "ROLLBACK"
end
-
+
if defined?(PGconn::PQTRANS_IDLE)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
@@ -920,18 +927,18 @@ def distinct(columns, order_by) #:nodoc:
sql = "DISTINCT ON (#{columns}) #{columns}, "
sql << order_columns * ', '
end
-
+
# Returns an ORDER BY clause for the passed order option.
- #
+ #
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
# by wrapping the +sql+ string as a sub-select and ordering in that query.
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
return sql if options[:order].blank?
-
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
-
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
end
@@ -1055,7 +1062,7 @@ def select_raw(sql, name = nil)
if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
- # (1) $12,345,678.12
+ # (1) $12,345,678.12
# (2) $12.345.678,12
case column = row[cell_index]
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
@@ -1115,3 +1122,4 @@ def extract_pg_identifier_from_name(name)
end
end
end
+
28 activerecord/test/cases/migration_test.rb
View
@@ -396,7 +396,7 @@ def test_add_column_with_precision_and_scale
assert_equal 9, wealth_column.precision
assert_equal 7, wealth_column.scale
end
-
+
def test_native_types
Person.delete_all
Person.connection.add_column "people", "last_name", :string
@@ -975,9 +975,9 @@ def test_migrator_one_up
def test_migrator_one_down
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
+
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
-
+
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
assert !Reminder.table_exists?
@@ -1118,20 +1118,20 @@ def test_migrator_going_down_due_to_version_target
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
end
-
+
def test_migrator_rollback
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
assert_equal(3, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(2, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(1, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
-
+
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
end
@@ -1294,7 +1294,7 @@ def with_env_tz(new_tz = 'US/Eastern')
end
end
-
+
class SexyMigrationsTest < ActiveRecord::TestCase
def test_references_column_type_adds_id
with_new_table do |t|
@@ -1350,6 +1350,15 @@ def test_string_creates_string_column
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_xml_creates_xml_column
+ with_new_table do |t|
+ t.expects(:column).with(:data, 'xml', {})
+ t.xml :data
+ end
+ end
+ end
+
protected
def with_new_table
Person.connection.create_table :delete_me, :force => true do |t|
@@ -1567,3 +1576,4 @@ def with_change_table
end
end
end
+
12 activerecord/test/cases/schema_dumper_test.rb
View
@@ -161,7 +161,7 @@ def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
end
-
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -196,6 +196,15 @@ def test_schema_dump_includes_decimal_options
assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_xml_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_xml_data_type"} =~ output
+ assert_match %r{t.xml "data"}, output
+ end
+ end
+ end
+
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
@@ -214,3 +223,4 @@ def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added
assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved"
end
end
+
15 activerecord/test/schema/postgresql_specific_schema.rb
View
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
- postgresql_oids defaults geometrics).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -100,4 +100,15 @@
obj_id OID
);
_SQL
-end
+
+ begin
+ execute <<_SQL
+ CREATE TABLE postgresql_xml_data_type (
+ id SERIAL PRIMARY KEY,
+ data xml
+ );
+_SQL
+rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
+ end
+end
+

1 comment on commit 0c391b4

Roderick van Domburg

Cool! On line 380 of postgresql_adapter.rb, the "E" quoting syntax should probably be replaced with #{quoted_string_prefix} for consistency.

Please sign in to comment.
Something went wrong with that request. Please try again.