activerecord_authorails 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|