activerecord_authorails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3043 -0
- data/README +360 -0
- data/RUNNING_UNIT_TESTS +64 -0
- data/Rakefile +226 -0
- data/examples/associations.png +0 -0
- data/examples/associations.rb +87 -0
- data/examples/shared_setup.rb +15 -0
- data/examples/validation.rb +85 -0
- data/install.rb +30 -0
- data/lib/active_record.rb +85 -0
- data/lib/active_record/acts/list.rb +244 -0
- data/lib/active_record/acts/nested_set.rb +211 -0
- data/lib/active_record/acts/tree.rb +89 -0
- data/lib/active_record/aggregations.rb +191 -0
- data/lib/active_record/associations.rb +1637 -0
- data/lib/active_record/associations/association_collection.rb +190 -0
- data/lib/active_record/associations/association_proxy.rb +158 -0
- data/lib/active_record/associations/belongs_to_association.rb +56 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
- data/lib/active_record/associations/has_many_association.rb +210 -0
- data/lib/active_record/associations/has_many_through_association.rb +247 -0
- data/lib/active_record/associations/has_one_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +2164 -0
- data/lib/active_record/calculations.rb +270 -0
- data/lib/active_record/callbacks.rb +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
- data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
- data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
- data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
- data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
- data/lib/active_record/deprecated_associations.rb +104 -0
- data/lib/active_record/deprecated_finders.rb +44 -0
- data/lib/active_record/fixtures.rb +628 -0
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +394 -0
- data/lib/active_record/observer.rb +178 -0
- data/lib/active_record/query_cache.rb +64 -0
- data/lib/active_record/reflection.rb +222 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +149 -0
- data/lib/active_record/timestamp.rb +51 -0
- data/lib/active_record/transactions.rb +136 -0
- data/lib/active_record/validations.rb +843 -0
- data/lib/active_record/vendor/db2.rb +362 -0
- data/lib/active_record/vendor/mysql.rb +1214 -0
- data/lib/active_record/vendor/simple.rb +693 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
- data/lib/active_record/wrappings.rb +58 -0
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +59 -0
- data/test/abstract_unit.rb +77 -0
- data/test/active_schema_test_mysql.rb +31 -0
- data/test/adapter_test.rb +87 -0
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +95 -0
- data/test/all.sh +8 -0
- data/test/ar_schema_test.rb +33 -0
- data/test/association_inheritance_reload.rb +14 -0
- data/test/associations/callbacks_test.rb +126 -0
- data/test/associations/cascaded_eager_loading_test.rb +138 -0
- data/test/associations/eager_test.rb +393 -0
- data/test/associations/extension_test.rb +42 -0
- data/test/associations/join_model_test.rb +497 -0
- data/test/associations_test.rb +1809 -0
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +1586 -0
- data/test/binary_test.rb +37 -0
- data/test/calculations_test.rb +219 -0
- data/test/callbacks_test.rb +377 -0
- data/test/class_inheritable_attributes_test.rb +32 -0
- data/test/column_alias_test.rb +17 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +25 -0
- data/test/connections/native_firebird/connection.rb +26 -0
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +24 -0
- data/test/connections/native_openbase/connection.rb +21 -0
- data/test/connections/native_oracle/connection.rb +27 -0
- data/test/connections/native_postgresql/connection.rb +23 -0
- data/test/connections/native_sqlite/connection.rb +34 -0
- data/test/connections/native_sqlite3/connection.rb +34 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sqlserver/connection.rb +23 -0
- data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/copy_table_sqlite.rb +64 -0
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/default_test_firebird.rb +16 -0
- data/test/defaults_test.rb +60 -0
- data/test/deprecated_associations_test.rb +396 -0
- data/test/deprecated_finder_test.rb +151 -0
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +504 -0
- data/test/fixtures/accounts.yml +28 -0
- data/test/fixtures/author.rb +99 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +7 -0
- data/test/fixtures/auto_id.rb +4 -0
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
- data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
- data/test/fixtures/bad_fixtures/blank_line +3 -0
- data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
- data/test/fixtures/bad_fixtures/missing_value +1 -0
- data/test/fixtures/binary.rb +2 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/categories_posts.yml +23 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +17 -0
- data/test/fixtures/category.rb +20 -0
- data/test/fixtures/column_name.rb +3 -0
- data/test/fixtures/comment.rb +23 -0
- data/test/fixtures/comments.yml +59 -0
- data/test/fixtures/companies.yml +55 -0
- data/test/fixtures/company.rb +107 -0
- data/test/fixtures/company_in_module.rb +59 -0
- data/test/fixtures/computer.rb +3 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/course.rb +3 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customer.rb +55 -0
- data/test/fixtures/customers.yml +17 -0
- data/test/fixtures/db_definitions/db2.drop.sql +32 -0
- data/test/fixtures/db_definitions/db2.sql +231 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +5 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
- data/test/fixtures/db_definitions/firebird.sql +304 -0
- data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
- data/test/fixtures/db_definitions/firebird2.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
- data/test/fixtures/db_definitions/frontbase.sql +268 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
- data/test/fixtures/db_definitions/mysql.sql +234 -0
- data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql2.sql +5 -0
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +302 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
- data/test/fixtures/db_definitions/oracle.sql +325 -0
- data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle2.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
- data/test/fixtures/db_definitions/postgresql.sql +263 -0
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +5 -0
- data/test/fixtures/db_definitions/schema.rb +60 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
- data/test/fixtures/db_definitions/sqlite.sql +215 -0
- data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite2.sql +5 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
- data/test/fixtures/db_definitions/sqlserver.sql +243 -0
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
- data/test/fixtures/db_definitions/sybase.sql +218 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/default.rb +2 -0
- data/test/fixtures/developer.rb +52 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/developers_projects/david_action_controller +3 -0
- data/test/fixtures/developers_projects/david_active_record +3 -0
- data/test/fixtures/developers_projects/jamis_active_record +2 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrant.rb +3 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/funny_jokes.yml +10 -0
- data/test/fixtures/joke.rb +6 -0
- data/test/fixtures/keyboard.rb +3 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
- data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixed_case_monkey.rb +3 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixin.rb +63 -0
- data/test/fixtures/mixins.yml +127 -0
- data/test/fixtures/movie.rb +5 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/order.rb +4 -0
- data/test/fixtures/people.yml +3 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/post.rb +58 -0
- data/test/fixtures/posts.yml +48 -0
- data/test/fixtures/project.rb +27 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +37 -0
- data/test/fixtures/subject.rb +4 -0
- data/test/fixtures/subscriber.rb +6 -0
- data/test/fixtures/subscribers/first +2 -0
- data/test/fixtures/subscribers/second +2 -0
- data/test/fixtures/tag.rb +7 -0
- data/test/fixtures/tagging.rb +6 -0
- data/test/fixtures/taggings.yml +18 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/task.rb +3 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures/topic.rb +25 -0
- data/test/fixtures/topics.yml +22 -0
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +401 -0
- data/test/inheritance_test.rb +205 -0
- data/test/lifecycle_test.rb +137 -0
- data/test/locking_test.rb +190 -0
- data/test/method_scoping_test.rb +416 -0
- data/test/migration_test.rb +768 -0
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +196 -0
- data/test/mixin_test.rb +550 -0
- data/test/modules_test.rb +34 -0
- data/test/multiple_db_test.rb +60 -0
- data/test/pk_test.rb +104 -0
- data/test/readonly_test.rb +107 -0
- data/test/reflection_test.rb +159 -0
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +96 -0
- data/test/schema_test_postgresql.rb +64 -0
- data/test/synonym_test_oracle.rb +17 -0
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +48 -0
- data/test/transactions_test.rb +230 -0
- data/test/unconnected_test.rb +32 -0
- data/test/validations_test.rb +1097 -0
- data/test/xml_serialization_test.rb +125 -0
- metadata +365 -0
@@ -0,0 +1,584 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Base
|
5
|
+
# Establishes a connection to the database that's used by all Active Record objects
|
6
|
+
def self.postgresql_connection(config) # :nodoc:
|
7
|
+
require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)
|
8
|
+
|
9
|
+
config = config.symbolize_keys
|
10
|
+
host = config[:host]
|
11
|
+
port = config[:port] || 5432
|
12
|
+
username = config[:username].to_s
|
13
|
+
password = config[:password].to_s
|
14
|
+
|
15
|
+
min_messages = config[:min_messages]
|
16
|
+
|
17
|
+
if config.has_key?(:database)
|
18
|
+
database = config[:database]
|
19
|
+
else
|
20
|
+
raise ArgumentError, "No database specified. Missing argument: database."
|
21
|
+
end
|
22
|
+
|
23
|
+
pga = ConnectionAdapters::PostgreSQLAdapter.new(
|
24
|
+
PGconn.connect(host, port, "", "", database, username, password), logger, config
|
25
|
+
)
|
26
|
+
|
27
|
+
PGconn.translate_results = false if PGconn.respond_to? :translate_results=
|
28
|
+
|
29
|
+
pga.schema_search_path = config[:schema_search_path] || config[:schema_order]
|
30
|
+
|
31
|
+
pga
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ConnectionAdapters
|
36
|
+
# The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base
|
37
|
+
# (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers.
|
38
|
+
#
|
39
|
+
# Options:
|
40
|
+
#
|
41
|
+
# * <tt>:host</tt> -- Defaults to localhost
|
42
|
+
# * <tt>:port</tt> -- Defaults to 5432
|
43
|
+
# * <tt>:username</tt> -- Defaults to nothing
|
44
|
+
# * <tt>:password</tt> -- Defaults to nothing
|
45
|
+
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
46
|
+
# * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
|
47
|
+
# * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
|
48
|
+
# * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
|
49
|
+
# * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
|
50
|
+
class PostgreSQLAdapter < AbstractAdapter
|
51
|
+
def adapter_name
|
52
|
+
'PostgreSQL'
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(connection, logger, config = {})
|
56
|
+
super(connection, logger)
|
57
|
+
@config = config
|
58
|
+
@async = config[:allow_concurrency]
|
59
|
+
configure_connection
|
60
|
+
end
|
61
|
+
|
62
|
+
# Is this connection alive and ready for queries?
|
63
|
+
def active?
|
64
|
+
if @connection.respond_to?(:status)
|
65
|
+
@connection.status == PGconn::CONNECTION_OK
|
66
|
+
else
|
67
|
+
@connection.query 'SELECT 1'
|
68
|
+
true
|
69
|
+
end
|
70
|
+
# postgres-pr raises a NoMethodError when querying if no conn is available
|
71
|
+
rescue PGError, NoMethodError
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
# Close then reopen the connection.
|
76
|
+
def reconnect!
|
77
|
+
# TODO: postgres-pr doesn't have PGconn#reset.
|
78
|
+
if @connection.respond_to?(:reset)
|
79
|
+
@connection.reset
|
80
|
+
configure_connection
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def disconnect!
|
85
|
+
# Both postgres and postgres-pr respond to :close
|
86
|
+
@connection.close rescue nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def native_database_types
|
90
|
+
{
|
91
|
+
:primary_key => "serial primary key",
|
92
|
+
:string => { :name => "character varying", :limit => 255 },
|
93
|
+
:text => { :name => "text" },
|
94
|
+
:integer => { :name => "integer" },
|
95
|
+
:float => { :name => "float" },
|
96
|
+
:decimal => { :name => "decimal" },
|
97
|
+
:datetime => { :name => "timestamp" },
|
98
|
+
:timestamp => { :name => "timestamp" },
|
99
|
+
:time => { :name => "time" },
|
100
|
+
:date => { :name => "date" },
|
101
|
+
:binary => { :name => "bytea" },
|
102
|
+
:boolean => { :name => "boolean" }
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def supports_migrations?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
def table_alias_length
|
111
|
+
63
|
112
|
+
end
|
113
|
+
|
114
|
+
# QUOTING ==================================================
|
115
|
+
|
116
|
+
def quote(value, column = nil)
|
117
|
+
if value.kind_of?(String) && column && column.type == :binary
|
118
|
+
"'#{escape_bytea(value)}'"
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def quote_column_name(name)
|
125
|
+
%("#{name}")
|
126
|
+
end
|
127
|
+
|
128
|
+
def quoted_date(value)
|
129
|
+
value.strftime("%Y-%m-%d %H:%M:%S.#{sprintf("%06d", value.usec)}")
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
# DATABASE STATEMENTS ======================================
|
134
|
+
|
135
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
136
|
+
execute(sql, name)
|
137
|
+
table = sql.split(" ", 4)[2]
|
138
|
+
id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
|
139
|
+
end
|
140
|
+
|
141
|
+
def query(sql, name = nil) #:nodoc:
|
142
|
+
log(sql, name) do
|
143
|
+
if @async
|
144
|
+
@connection.async_query(sql)
|
145
|
+
else
|
146
|
+
@connection.query(sql)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def execute(sql, name = nil) #:nodoc:
|
152
|
+
log(sql, name) do
|
153
|
+
if @async
|
154
|
+
@connection.async_exec(sql)
|
155
|
+
else
|
156
|
+
@connection.exec(sql)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def update(sql, name = nil) #:nodoc:
|
162
|
+
execute(sql, name).cmdtuples
|
163
|
+
end
|
164
|
+
|
165
|
+
def begin_db_transaction #:nodoc:
|
166
|
+
execute "BEGIN"
|
167
|
+
end
|
168
|
+
|
169
|
+
def commit_db_transaction #:nodoc:
|
170
|
+
execute "COMMIT"
|
171
|
+
end
|
172
|
+
|
173
|
+
def rollback_db_transaction #:nodoc:
|
174
|
+
execute "ROLLBACK"
|
175
|
+
end
|
176
|
+
|
177
|
+
# SCHEMA STATEMENTS ========================================
|
178
|
+
|
179
|
+
# Return the list of all tables in the schema search path.
|
180
|
+
def tables(name = nil) #:nodoc:
|
181
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
182
|
+
query(<<-SQL, name).map { |row| row[0] }
|
183
|
+
SELECT tablename
|
184
|
+
FROM pg_tables
|
185
|
+
WHERE schemaname IN (#{schemas})
|
186
|
+
SQL
|
187
|
+
end
|
188
|
+
|
189
|
+
def indexes(table_name, name = nil) #:nodoc:
|
190
|
+
result = query(<<-SQL, name)
|
191
|
+
SELECT i.relname, d.indisunique, a.attname
|
192
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a
|
193
|
+
WHERE i.relkind = 'i'
|
194
|
+
AND d.indexrelid = i.oid
|
195
|
+
AND d.indisprimary = 'f'
|
196
|
+
AND t.oid = d.indrelid
|
197
|
+
AND t.relname = '#{table_name}'
|
198
|
+
AND a.attrelid = t.oid
|
199
|
+
AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
|
200
|
+
OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
|
201
|
+
OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
|
202
|
+
OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
|
203
|
+
OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
|
204
|
+
ORDER BY i.relname
|
205
|
+
SQL
|
206
|
+
|
207
|
+
current_index = nil
|
208
|
+
indexes = []
|
209
|
+
|
210
|
+
result.each do |row|
|
211
|
+
if current_index != row[0]
|
212
|
+
indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
|
213
|
+
current_index = row[0]
|
214
|
+
end
|
215
|
+
|
216
|
+
indexes.last.columns << row[2]
|
217
|
+
end
|
218
|
+
|
219
|
+
indexes
|
220
|
+
end
|
221
|
+
|
222
|
+
def columns(table_name, name = nil) #:nodoc:
|
223
|
+
column_definitions(table_name).collect do |name, type, default, notnull, typmod|
|
224
|
+
# typmod now unused as limit, precision, scale all handled by superclass
|
225
|
+
Column.new(name, default_value(default), translate_field_type(type), notnull == "f")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Set the schema search path to a string of comma-separated schema names.
|
230
|
+
# Names beginning with $ are quoted (e.g. $user => '$user')
|
231
|
+
# See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
|
232
|
+
def schema_search_path=(schema_csv) #:nodoc:
|
233
|
+
if schema_csv
|
234
|
+
execute "SET search_path TO #{schema_csv}"
|
235
|
+
@schema_search_path = nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def schema_search_path #:nodoc:
|
240
|
+
@schema_search_path ||= query('SHOW search_path')[0][0]
|
241
|
+
end
|
242
|
+
|
243
|
+
def default_sequence_name(table_name, pk = nil)
|
244
|
+
default_pk, default_seq = pk_and_sequence_for(table_name)
|
245
|
+
default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Resets sequence to the max value of the table's pk if present.
|
249
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
250
|
+
unless pk and sequence
|
251
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
252
|
+
pk ||= default_pk
|
253
|
+
sequence ||= default_sequence
|
254
|
+
end
|
255
|
+
if pk
|
256
|
+
if sequence
|
257
|
+
select_value <<-end_sql, 'Reset sequence'
|
258
|
+
SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
|
259
|
+
end_sql
|
260
|
+
else
|
261
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Find a table's primary key and sequence.
|
267
|
+
def pk_and_sequence_for(table)
|
268
|
+
# First try looking for a sequence with a dependency on the
|
269
|
+
# given table's primary key.
|
270
|
+
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
271
|
+
SELECT attr.attname, name.nspname, seq.relname
|
272
|
+
FROM pg_class seq,
|
273
|
+
pg_attribute attr,
|
274
|
+
pg_depend dep,
|
275
|
+
pg_namespace name,
|
276
|
+
pg_constraint cons
|
277
|
+
WHERE seq.oid = dep.objid
|
278
|
+
AND seq.relnamespace = name.oid
|
279
|
+
AND seq.relkind = 'S'
|
280
|
+
AND attr.attrelid = dep.refobjid
|
281
|
+
AND attr.attnum = dep.refobjsubid
|
282
|
+
AND attr.attrelid = cons.conrelid
|
283
|
+
AND attr.attnum = cons.conkey[1]
|
284
|
+
AND cons.contype = 'p'
|
285
|
+
AND dep.refobjid = '#{table}'::regclass
|
286
|
+
end_sql
|
287
|
+
|
288
|
+
if result.nil? or result.empty?
|
289
|
+
# If that fails, try parsing the primary key's default value.
|
290
|
+
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
291
|
+
# the 8.1+ nextval('foo'::regclass).
|
292
|
+
# TODO: assumes sequence is in same schema as table.
|
293
|
+
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
294
|
+
SELECT attr.attname, name.nspname, split_part(def.adsrc, '''', 2)
|
295
|
+
FROM pg_class t
|
296
|
+
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
297
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
298
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
299
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
300
|
+
WHERE t.oid = '#{table}'::regclass
|
301
|
+
AND cons.contype = 'p'
|
302
|
+
AND def.adsrc ~* 'nextval'
|
303
|
+
end_sql
|
304
|
+
end
|
305
|
+
# check for existence of . in sequence name as in public.foo_sequence. if it does not exist, return unqualified sequence
|
306
|
+
# We cannot qualify unqualified sequences, as rails doesn't qualify any table access, using the search path
|
307
|
+
[result.first, result.last]
|
308
|
+
rescue
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
|
312
|
+
def rename_table(name, new_name)
|
313
|
+
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
314
|
+
end
|
315
|
+
|
316
|
+
def add_column(table_name, column_name, type, options = {})
|
317
|
+
default = options[:default]
|
318
|
+
notnull = options[:null] == false
|
319
|
+
|
320
|
+
# Add the column.
|
321
|
+
execute("ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit])}")
|
322
|
+
|
323
|
+
# Set optional default. If not null, update nulls to the new default.
|
324
|
+
if options_include_default?(options)
|
325
|
+
change_column_default(table_name, column_name, default)
|
326
|
+
if notnull
|
327
|
+
execute("UPDATE #{table_name} SET #{column_name}=#{quote(default, options[:column])} WHERE #{column_name} IS NULL")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
if notnull
|
332
|
+
execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
337
|
+
begin
|
338
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
339
|
+
rescue ActiveRecord::StatementInvalid
|
340
|
+
# This is PG7, so we use a more arcane way of doing it.
|
341
|
+
begin_db_transaction
|
342
|
+
add_column(table_name, "#{column_name}_ar_tmp", type, options)
|
343
|
+
execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
344
|
+
remove_column(table_name, column_name)
|
345
|
+
rename_column(table_name, "#{column_name}_ar_tmp", column_name)
|
346
|
+
commit_db_transaction
|
347
|
+
end
|
348
|
+
|
349
|
+
if options_include_default?(options)
|
350
|
+
change_column_default(table_name, column_name, options[:default])
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
355
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
356
|
+
end
|
357
|
+
|
358
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
359
|
+
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
360
|
+
end
|
361
|
+
|
362
|
+
def remove_index(table_name, options) #:nodoc:
|
363
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
364
|
+
end
|
365
|
+
|
366
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
367
|
+
return super unless type.to_s == 'integer'
|
368
|
+
|
369
|
+
if limit.nil? || limit == 4
|
370
|
+
'integer'
|
371
|
+
elsif limit < 4
|
372
|
+
'smallint'
|
373
|
+
else
|
374
|
+
'bigint'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
379
|
+
#
|
380
|
+
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
381
|
+
# requires that the ORDER BY include the distinct column.
|
382
|
+
#
|
383
|
+
# distinct("posts.id", "posts.created_at desc")
|
384
|
+
def distinct(columns, order_by)
|
385
|
+
return "DISTINCT #{columns}" if order_by.blank?
|
386
|
+
|
387
|
+
# construct a clean list of column names from the ORDER BY clause, removing
|
388
|
+
# any asc/desc modifiers
|
389
|
+
order_columns = order_by.split(',').collect { |s| s.split.first }
|
390
|
+
order_columns.delete_if &:blank?
|
391
|
+
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
392
|
+
|
393
|
+
# return a DISTINCT ON() clause that's distinct on the columns we want but includes
|
394
|
+
# all the required columns for the ORDER BY to work properly
|
395
|
+
sql = "DISTINCT ON (#{columns}) #{columns}, "
|
396
|
+
sql << order_columns * ', '
|
397
|
+
end
|
398
|
+
|
399
|
+
# ORDER BY clause for the passed order option.
|
400
|
+
#
|
401
|
+
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
402
|
+
# by wrapping the sql as a sub-select and ordering in that query.
|
403
|
+
def add_order_by_for_association_limiting!(sql, options)
|
404
|
+
return sql if options[:order].blank?
|
405
|
+
|
406
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
407
|
+
order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
|
408
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
|
409
|
+
|
410
|
+
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
|
411
|
+
end
|
412
|
+
|
413
|
+
private
|
414
|
+
BYTEA_COLUMN_TYPE_OID = 17
|
415
|
+
NUMERIC_COLUMN_TYPE_OID = 1700
|
416
|
+
TIMESTAMPOID = 1114
|
417
|
+
TIMESTAMPTZOID = 1184
|
418
|
+
|
419
|
+
def configure_connection
|
420
|
+
if @config[:encoding]
|
421
|
+
execute("SET client_encoding TO '#{@config[:encoding]}'")
|
422
|
+
end
|
423
|
+
if @config[:min_messages]
|
424
|
+
execute("SET client_min_messages TO '#{@config[:min_messages]}'")
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def last_insert_id(table, sequence_name)
|
429
|
+
Integer(select_value("SELECT currval('#{sequence_name}')"))
|
430
|
+
end
|
431
|
+
|
432
|
+
def select(sql, name = nil)
|
433
|
+
res = execute(sql, name)
|
434
|
+
results = res.result
|
435
|
+
rows = []
|
436
|
+
if results.length > 0
|
437
|
+
fields = res.fields
|
438
|
+
results.each do |row|
|
439
|
+
hashed_row = {}
|
440
|
+
row.each_index do |cel_index|
|
441
|
+
column = row[cel_index]
|
442
|
+
|
443
|
+
case res.type(cel_index)
|
444
|
+
when BYTEA_COLUMN_TYPE_OID
|
445
|
+
column = unescape_bytea(column)
|
446
|
+
when TIMESTAMPTZOID, TIMESTAMPOID
|
447
|
+
column = cast_to_time(column)
|
448
|
+
when NUMERIC_COLUMN_TYPE_OID
|
449
|
+
column = column.to_d if column.respond_to?(:to_d)
|
450
|
+
end
|
451
|
+
|
452
|
+
hashed_row[fields[cel_index]] = column
|
453
|
+
end
|
454
|
+
rows << hashed_row
|
455
|
+
end
|
456
|
+
end
|
457
|
+
res.clear
|
458
|
+
return rows
|
459
|
+
end
|
460
|
+
|
461
|
+
def escape_bytea(s)
|
462
|
+
if PGconn.respond_to? :escape_bytea
|
463
|
+
self.class.send(:define_method, :escape_bytea) do |s|
|
464
|
+
PGconn.escape_bytea(s) if s
|
465
|
+
end
|
466
|
+
else
|
467
|
+
self.class.send(:define_method, :escape_bytea) do |s|
|
468
|
+
if s
|
469
|
+
result = ''
|
470
|
+
s.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
471
|
+
result
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
escape_bytea(s)
|
476
|
+
end
|
477
|
+
|
478
|
+
def unescape_bytea(s)
|
479
|
+
if PGconn.respond_to? :unescape_bytea
|
480
|
+
self.class.send(:define_method, :unescape_bytea) do |s|
|
481
|
+
PGconn.unescape_bytea(s) if s
|
482
|
+
end
|
483
|
+
else
|
484
|
+
self.class.send(:define_method, :unescape_bytea) do |s|
|
485
|
+
if s
|
486
|
+
result = ''
|
487
|
+
i, max = 0, s.size
|
488
|
+
while i < max
|
489
|
+
char = s[i]
|
490
|
+
if char == ?\\
|
491
|
+
if s[i+1] == ?\\
|
492
|
+
char = ?\\
|
493
|
+
i += 1
|
494
|
+
else
|
495
|
+
char = s[i+1..i+3].oct
|
496
|
+
i += 3
|
497
|
+
end
|
498
|
+
end
|
499
|
+
result << char
|
500
|
+
i += 1
|
501
|
+
end
|
502
|
+
result
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
unescape_bytea(s)
|
507
|
+
end
|
508
|
+
|
509
|
+
# Query a table's column names, default values, and types.
|
510
|
+
#
|
511
|
+
# The underlying query is roughly:
|
512
|
+
# SELECT column.name, column.type, default.value
|
513
|
+
# FROM column LEFT JOIN default
|
514
|
+
# ON column.table_id = default.table_id
|
515
|
+
# AND column.num = default.column_num
|
516
|
+
# WHERE column.table_id = get_table_id('table_name')
|
517
|
+
# AND column.num > 0
|
518
|
+
# AND NOT column.is_dropped
|
519
|
+
# ORDER BY column.num
|
520
|
+
#
|
521
|
+
# If the table name is not prefixed with a schema, the database will
|
522
|
+
# take the first match from the schema search path.
|
523
|
+
#
|
524
|
+
# Query implementation notes:
|
525
|
+
# - format_type includes the column size constraint, e.g. varchar(50)
|
526
|
+
# - ::regclass is a function that gives the id for a table name
|
527
|
+
def column_definitions(table_name)
|
528
|
+
query <<-end_sql
|
529
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
|
530
|
+
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
531
|
+
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
532
|
+
WHERE a.attrelid = '#{table_name}'::regclass
|
533
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
534
|
+
ORDER BY a.attnum
|
535
|
+
end_sql
|
536
|
+
end
|
537
|
+
|
538
|
+
# Translate PostgreSQL-specific types into simplified SQL types.
|
539
|
+
# These are special cases; standard types are handled by
|
540
|
+
# ConnectionAdapters::Column#simplified_type.
|
541
|
+
def translate_field_type(field_type)
|
542
|
+
# Match the beginning of field_type since it may have a size constraint on the end.
|
543
|
+
case field_type
|
544
|
+
# PostgreSQL array data types.
|
545
|
+
when /\[\]$/i then 'string'
|
546
|
+
when /^timestamp/i then 'datetime'
|
547
|
+
when /^real|^money/i then 'float'
|
548
|
+
when /^interval/i then 'string'
|
549
|
+
# geometric types (the line type is currently not implemented in postgresql)
|
550
|
+
when /^(?:point|lseg|box|"?path"?|polygon|circle)/i then 'string'
|
551
|
+
when /^bytea/i then 'binary'
|
552
|
+
else field_type # Pass through standard types.
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def default_value(value)
|
557
|
+
# Boolean types
|
558
|
+
return "t" if value =~ /true/i
|
559
|
+
return "f" if value =~ /false/i
|
560
|
+
|
561
|
+
# Char/String/Bytea type values
|
562
|
+
return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
|
563
|
+
|
564
|
+
# Numeric values
|
565
|
+
return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
|
566
|
+
|
567
|
+
# Fixed dates / times
|
568
|
+
return $1 if value =~ /^'(.+)'::(date|timestamp)/
|
569
|
+
|
570
|
+
# Anything else is blank, some user type, or some function
|
571
|
+
# and we can't know the value of that, so return nil.
|
572
|
+
return nil
|
573
|
+
end
|
574
|
+
|
575
|
+
# Only needed for DateTime instances
|
576
|
+
def cast_to_time(value)
|
577
|
+
return value unless value.class == DateTime
|
578
|
+
v = value
|
579
|
+
time_array = [v.year, v.month, v.day, v.hour, v.min, v.sec, v.usec]
|
580
|
+
Time.send(Base.default_timezone, *time_array) rescue nil
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|