pg, PostgreSQL::Name to hold schema qualified names.

This commit is contained in:
Yves Senn 2014-05-30 10:53:24 +02:00
parent 6963e33829
commit 7b8d95d58f
4 changed files with 96 additions and 19 deletions

@ -150,12 +150,7 @@ def quote_string(s) #:nodoc:
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name)
schema, table = Utils.extract_schema_and_table(name.to_s)
if schema
"#{quote_column_name(schema)}.#{quote_column_name(table)}"
else
quote_column_name(table)
end
Utils.extract_schema_qualified_name(name.to_s).quoted
end
def quote_table_name_for_assignment(table, attr)

@ -97,16 +97,16 @@ def tables(name = nil)
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
schema, table = Utils.extract_schema_and_table(name.to_s)
return false unless table
name = Utils.extract_schema_qualified_name(name.to_s)
return false unless name.identifier
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
AND c.relname = '#{name.identifier}'
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end

@ -1,13 +1,54 @@
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
# Value Object to hold a schema qualified name.
# This is usually the name of a PostgreSQL relation but it can also represent
# schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
# double quoting.
class Name # :nodoc:
SEPARATOR = "."
attr_reader :schema, :identifier
def initialize(schema, identifier)
@schema, @identifier = unquote(schema), unquote(identifier)
end
def to_s
parts.join SEPARATOR
end
def quoted
parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
end
def ==(o)
o.class == self.class && o.parts == parts
end
alias_method :eql?, :==
def hash
parts.hash
end
protected
def unquote(part)
return unless part
part.gsub(/(^"|"$)/,'')
end
def parts
@parts ||= [@schema, @identifier].compact
end
end
module Utils # :nodoc:
extend self
# Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
# +schema_name+ is nil if not specified in +name+.
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
# Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
# extracted from +string+.
# +schema+ is nil if not specified in +string+.
# +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
# +string+ supports the range of schema/table references understood by PostgreSQL, for example:
#
# * <tt>table_name</tt>
# * <tt>"table.name"</tt>
@ -15,9 +56,9 @@ module Utils # :nodoc:
# * <tt>schema_name."table.name"</tt>
# * <tt>"schema_name".table_name</tt>
# * <tt>"schema.name"."table name"</tt>
def extract_schema_and_table(name)
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
[schema, table]
def extract_schema_qualified_name(string)
table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
PostgreSQL::Name.new(schema, table)
end
end
end

@ -1,9 +1,10 @@
require 'cases/helper'
class PostgreSQLUtilsTest < ActiveSupport::TestCase
Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
def test_extract_schema_and_table
def test_extract_schema_qualified_name
{
%(table_name) => [nil,'table_name'],
%("table.name") => [nil,'table.name'],
@ -14,7 +15,47 @@ def test_extract_schema_and_table
%("even spaces".table) => ['even spaces','table'],
%(schema."table.name") => ['schema', 'table.name']
}.each do |given, expect|
assert_equal expect, extract_schema_and_table(given)
assert_equal Name.new(*expect), extract_schema_qualified_name(given)
end
end
end
class PostgreSQLNameTest < ActiveSupport::TestCase
Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
test "represents itself as schema.name" do
obj = Name.new("public", "articles")
assert_equal "public.articles", obj.to_s
end
test "without schema, represents itself as name only" do
obj = Name.new(nil, "articles")
assert_equal "articles", obj.to_s
end
test "quoted returns a string representation usable in a query" do
assert_equal %("articles"), Name.new(nil, "articles").quoted
assert_equal %("public"."articles"), Name.new("public", "articles").quoted
end
test "prevents double quoting" do
name = Name.new('"quoted_schema"', '"quoted_table"')
assert_equal "quoted_schema.quoted_table", name.to_s
assert_equal %("quoted_schema"."quoted_table"), name.quoted
end
test "equality based on state" do
assert_equal Name.new("access", "users"), Name.new("access", "users")
assert_equal Name.new(nil, "users"), Name.new(nil, "users")
assert_not_equal Name.new(nil, "users"), Name.new("access", "users")
assert_not_equal Name.new("access", "users"), Name.new("public", "users")
assert_not_equal Name.new("public", "users"), Name.new("public", "articles")
end
test "can be used as hash key" do
hash = {Name.new("schema", "article_seq") => "success"}
assert_equal "success", hash[Name.new("schema", "article_seq")]
assert_equal nil, hash[Name.new("schema", "articles")]
assert_equal nil, hash[Name.new("public", "article_seq")]
end
end