activerecord 1.15.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +2454 -34
- data/README +1 -1
- data/RUNNING_UNIT_TESTS +3 -34
- data/Rakefile +98 -77
- data/install.rb +1 -1
- data/lib/active_record.rb +13 -22
- data/lib/active_record/aggregations.rb +38 -49
- data/lib/active_record/associations.rb +452 -333
- data/lib/active_record/associations/association_collection.rb +66 -20
- data/lib/active_record/associations/association_proxy.rb +9 -8
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +38 -18
- data/lib/active_record/associations/has_one_association.rb +30 -14
- data/lib/active_record/attribute_methods.rb +253 -0
- data/lib/active_record/base.rb +719 -494
- data/lib/active_record/calculations.rb +62 -63
- data/lib/active_record/callbacks.rb +57 -83
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
- data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
- data/lib/active_record/fixtures.rb +503 -113
- data/lib/active_record/locking/optimistic.rb +72 -34
- data/lib/active_record/migration.rb +80 -57
- data/lib/active_record/observer.rb +13 -10
- data/lib/active_record/query_cache.rb +16 -57
- data/lib/active_record/reflection.rb +35 -38
- data/lib/active_record/schema.rb +5 -5
- data/lib/active_record/schema_dumper.rb +35 -13
- data/lib/active_record/serialization.rb +98 -0
- data/lib/active_record/serializers/json_serializer.rb +71 -0
- data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
- data/lib/active_record/timestamp.rb +20 -21
- data/lib/active_record/transactions.rb +39 -43
- data/lib/active_record/validations.rb +256 -107
- data/lib/active_record/version.rb +3 -3
- data/lib/activerecord.rb +1 -0
- data/test/aaa_create_tables_test.rb +15 -2
- data/test/abstract_unit.rb +24 -17
- data/test/active_schema_test_mysql.rb +20 -8
- data/test/adapter_test.rb +23 -5
- data/test/adapter_test_sqlserver.rb +15 -1
- data/test/aggregations_test.rb +16 -1
- data/test/all.sh +2 -2
- data/test/associations/ar_joins_test.rb +0 -0
- data/test/associations/callbacks_test.rb +51 -30
- data/test/associations/cascaded_eager_loading_test.rb +1 -29
- data/test/associations/eager_singularization_test.rb +145 -0
- data/test/associations/eager_test.rb +42 -6
- data/test/associations/extension_test.rb +6 -1
- data/test/associations/inner_join_association_test.rb +88 -0
- data/test/associations/join_model_test.rb +47 -16
- data/test/associations_test.rb +449 -226
- data/test/attribute_methods_test.rb +97 -0
- data/test/base_test.rb +251 -105
- data/test/binary_test.rb +22 -27
- data/test/calculations_test.rb +37 -5
- data/test/callbacks_test.rb +23 -0
- data/test/connection_test_firebird.rb +2 -2
- data/test/connection_test_mysql.rb +30 -0
- data/test/connections/native_mysql/connection.rb +3 -0
- data/test/connections/native_sqlite/connection.rb +5 -14
- data/test/connections/native_sqlite3/connection.rb +5 -14
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
- data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
- data/test/datatype_test_postgresql.rb +178 -27
- data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
- data/test/defaults_test.rb +8 -1
- data/test/deprecated_finder_test.rb +7 -128
- data/test/finder_test.rb +192 -54
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author.rb +12 -5
- data/test/fixtures/binaries.yml +130 -435
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +8 -1
- data/test/fixtures/computer.rb +1 -0
- data/test/fixtures/contact.rb +16 -0
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +4 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
- data/test/fixtures/db_definitions/firebird.sql +6 -0
- data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase.sql +5 -0
- data/test/fixtures/db_definitions/openbase.sql +41 -25
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +5 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
- data/test/fixtures/db_definitions/postgresql.sql +87 -58
- data/test/fixtures/db_definitions/postgresql2.sql +1 -2
- data/test/fixtures/db_definitions/schema.rb +280 -0
- data/test/fixtures/db_definitions/schema2.rb +11 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +4 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
- data/test/fixtures/db_definitions/sybase.sql +4 -0
- data/test/fixtures/developer.rb +10 -0
- data/test/fixtures/example.log +1 -0
- data/test/fixtures/flowers.jpg +0 -0
- data/test/fixtures/item.rb +7 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/joke.rb +0 -3
- data/test/fixtures/matey.rb +4 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/minimalistic.rb +2 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixins.yml +2 -100
- data/test/fixtures/parrot.rb +13 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/pirate.rb +5 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/post.rb +1 -0
- data/test/fixtures/project.rb +3 -2
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ship.rb +3 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/tagging.rb +4 -0
- data/test/fixtures/taggings.yml +8 -1
- data/test/fixtures/topic.rb +13 -1
- data/test/fixtures/treasure.rb +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures_test.rb +205 -24
- data/test/inheritance_test.rb +7 -1
- data/test/json_serialization_test.rb +180 -0
- data/test/lifecycle_test.rb +1 -1
- data/test/locking_test.rb +85 -2
- data/test/migration_test.rb +206 -40
- data/test/mixin_test.rb +13 -515
- data/test/pk_test.rb +3 -6
- data/test/query_cache_test.rb +104 -0
- data/test/reflection_test.rb +16 -0
- data/test/reserved_word_test_mysql.rb +177 -0
- data/test/schema_dumper_test.rb +38 -3
- data/test/serialization_test.rb +47 -0
- data/test/transactions_test.rb +74 -23
- data/test/unconnected_test.rb +1 -1
- data/test/validations_test.rb +322 -32
- data/test/xml_serialization_test.rb +121 -44
- metadata +48 -41
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -85
- data/lib/active_record/acts/list.rb +0 -256
- data/lib/active_record/acts/nested_set.rb +0 -211
- data/lib/active_record/acts/tree.rb +0 -96
- data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
- data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
- data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
- data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
- data/lib/active_record/deprecated_associations.rb +0 -104
- data/lib/active_record/deprecated_finders.rb +0 -44
- data/lib/active_record/vendor/simple.rb +0 -693
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -58
- data/test/connections/native_sqlserver/connection.rb +0 -23
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
- data/test/deprecated_associations_test.rb +0 -396
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
- data/test/fixtures/db_definitions/mysql.sql +0 -234
- data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/mysql2.sql +0 -5
- data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
- data/test/fixtures/db_definitions/sqlserver.sql +0 -243
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
- data/test/fixtures/mixin.rb +0 -63
- data/test/mixin_nested_set_test.rb +0 -196
@@ -12,29 +12,205 @@ module ActiveRecord
|
|
12
12
|
username = config[:username].to_s
|
13
13
|
password = config[:password].to_s
|
14
14
|
|
15
|
-
min_messages = config[:min_messages]
|
16
|
-
|
17
15
|
if config.has_key?(:database)
|
18
16
|
database = config[:database]
|
19
17
|
else
|
20
18
|
raise ArgumentError, "No database specified. Missing argument: database."
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
)
|
21
|
+
# The postgres drivers don't allow the creation of an unconnected PGconn object,
|
22
|
+
# so just pass a nil connection object for the time being.
|
23
|
+
ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
|
24
|
+
end
|
25
|
+
end
|
26
26
|
|
27
|
-
|
27
|
+
module ConnectionAdapters
|
28
|
+
# PostgreSQL-specific extensions to column definitions in a table.
|
29
|
+
class PostgreSQLColumn < Column #:nodoc:
|
30
|
+
# Instantiates a new PostgreSQL column definition in a table.
|
31
|
+
def initialize(name, default, sql_type = nil, null = true)
|
32
|
+
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
33
|
+
end
|
28
34
|
|
29
|
-
|
35
|
+
private
|
36
|
+
# Extracts the scale from PostgreSQL-specific data types.
|
37
|
+
def extract_scale(sql_type)
|
38
|
+
# Money type has a fixed scale of 2.
|
39
|
+
sql_type =~ /^money/ ? 2 : super
|
40
|
+
end
|
30
41
|
|
31
|
-
|
42
|
+
# Extracts the precision from PostgreSQL-specific data types.
|
43
|
+
def extract_precision(sql_type)
|
44
|
+
# Actual code is defined dynamically in PostgreSQLAdapter.connect
|
45
|
+
# depending on the server specifics
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
# Escapes binary strings for bytea input to the database.
|
50
|
+
def self.string_to_binary(value)
|
51
|
+
if PGconn.respond_to?(:escape_bytea)
|
52
|
+
self.class.module_eval do
|
53
|
+
define_method(:string_to_binary) do |value|
|
54
|
+
PGconn.escape_bytea(value) if value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
else
|
58
|
+
self.class.module_eval do
|
59
|
+
define_method(:string_to_binary) do |value|
|
60
|
+
if value
|
61
|
+
result = ''
|
62
|
+
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
63
|
+
result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
self.class.string_to_binary(value)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Unescapes bytea output from a database to the binary string it represents.
|
72
|
+
def self.binary_to_string(value)
|
73
|
+
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
74
|
+
# or an unescaped Active Record attribute that was just written.
|
75
|
+
if PGconn.respond_to?(:unescape_bytea)
|
76
|
+
self.class.module_eval do
|
77
|
+
define_method(:binary_to_string) do |value|
|
78
|
+
if value =~ /\\\d{3}/
|
79
|
+
PGconn.unescape_bytea(value)
|
80
|
+
else
|
81
|
+
value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
self.class.module_eval do
|
87
|
+
define_method(:binary_to_string) do |value|
|
88
|
+
if value =~ /\\\d{3}/
|
89
|
+
result = ''
|
90
|
+
i, max = 0, value.size
|
91
|
+
while i < max
|
92
|
+
char = value[i]
|
93
|
+
if char == ?\\
|
94
|
+
if value[i+1] == ?\\
|
95
|
+
char = ?\\
|
96
|
+
i += 1
|
97
|
+
else
|
98
|
+
char = value[i+1..i+3].oct
|
99
|
+
i += 3
|
100
|
+
end
|
101
|
+
end
|
102
|
+
result << char
|
103
|
+
i += 1
|
104
|
+
end
|
105
|
+
result
|
106
|
+
else
|
107
|
+
value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
self.class.binary_to_string(value)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Maps PostgreSQL-specific data types to logical Rails types.
|
116
|
+
def simplified_type(field_type)
|
117
|
+
case field_type
|
118
|
+
# Numeric and monetary types
|
119
|
+
when /^(?:real|double precision)$/
|
120
|
+
:float
|
121
|
+
# Monetary types
|
122
|
+
when /^money$/
|
123
|
+
:decimal
|
124
|
+
# Character types
|
125
|
+
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
|
126
|
+
:string
|
127
|
+
# Binary data types
|
128
|
+
when /^bytea$/
|
129
|
+
:binary
|
130
|
+
# Date/time types
|
131
|
+
when /^timestamp with(?:out)? time zone$/
|
132
|
+
:datetime
|
133
|
+
when /^interval$/
|
134
|
+
:string
|
135
|
+
# Geometric types
|
136
|
+
when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
|
137
|
+
:string
|
138
|
+
# Network address types
|
139
|
+
when /^(?:cidr|inet|macaddr)$/
|
140
|
+
:string
|
141
|
+
# Bit strings
|
142
|
+
when /^bit(?: varying)?(?:\(\d+\))?$/
|
143
|
+
:string
|
144
|
+
# XML type
|
145
|
+
when /^xml$/
|
146
|
+
:string
|
147
|
+
# Arrays
|
148
|
+
when /^\D+\[\]$/
|
149
|
+
:string
|
150
|
+
# Object identifier types
|
151
|
+
when /^oid$/
|
152
|
+
:integer
|
153
|
+
# Pass through all types that are not specific to PostgreSQL.
|
154
|
+
else
|
155
|
+
super
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Extracts the value from a PostgreSQL column default definition.
|
160
|
+
def self.extract_value_from_default(default)
|
161
|
+
case default
|
162
|
+
# Numeric types
|
163
|
+
when /\A-?\d+(\.\d*)?\z/
|
164
|
+
default
|
165
|
+
# Character types
|
166
|
+
when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
|
167
|
+
$1
|
168
|
+
# Character types (8.1 formatting)
|
169
|
+
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
170
|
+
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
171
|
+
# Binary data types
|
172
|
+
when /\A'(.*)'::bytea\z/m
|
173
|
+
$1
|
174
|
+
# Date/time types
|
175
|
+
when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
|
176
|
+
$1
|
177
|
+
when /\A'(.*)'::interval\z/
|
178
|
+
$1
|
179
|
+
# Boolean type
|
180
|
+
when 'true'
|
181
|
+
true
|
182
|
+
when 'false'
|
183
|
+
false
|
184
|
+
# Geometric types
|
185
|
+
when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
|
186
|
+
$1
|
187
|
+
# Network address types
|
188
|
+
when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
|
189
|
+
$1
|
190
|
+
# Bit string types
|
191
|
+
when /\AB'(.*)'::"?bit(?: varying)?"?\z/
|
192
|
+
$1
|
193
|
+
# XML type
|
194
|
+
when /\A'(.*)'::xml\z/m
|
195
|
+
$1
|
196
|
+
# Arrays
|
197
|
+
when /\A'(.*)'::"?\D+"?\[\]\z/
|
198
|
+
$1
|
199
|
+
# Object identifier types
|
200
|
+
when /\A-?\d+\z/
|
201
|
+
$1
|
202
|
+
else
|
203
|
+
# Anything else is blank, some user type, or some function
|
204
|
+
# and we can't know the value of that, so return nil.
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
end
|
32
208
|
end
|
33
209
|
end
|
34
210
|
|
35
211
|
module ConnectionAdapters
|
36
|
-
# The PostgreSQL adapter works both with the C
|
37
|
-
# (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=
|
212
|
+
# The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
|
213
|
+
# Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
|
38
214
|
#
|
39
215
|
# Options:
|
40
216
|
#
|
@@ -44,19 +220,21 @@ module ActiveRecord
|
|
44
220
|
# * <tt>:password</tt> -- Defaults to nothing
|
45
221
|
# * <tt>:database</tt> -- The name of the database. No default, must be provided.
|
46
222
|
# * <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
|
48
|
-
# * <tt>:min_messages</tt> -- An optional client min messages that is
|
223
|
+
# * <tt>:encoding</tt> -- An optional client encoding that is used in a SET client_encoding TO <encoding> call on the connection.
|
224
|
+
# * <tt>:min_messages</tt> -- An optional client min messages that is used in a SET client_min_messages TO <min_messages> call on the connection.
|
49
225
|
# * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
|
50
226
|
class PostgreSQLAdapter < AbstractAdapter
|
227
|
+
# Returns 'PostgreSQL' as adapter name for identification purposes.
|
51
228
|
def adapter_name
|
52
229
|
'PostgreSQL'
|
53
230
|
end
|
54
231
|
|
55
|
-
|
232
|
+
# Initializes and connects a PostgreSQL adapter.
|
233
|
+
def initialize(connection, logger, connection_parameters, config)
|
56
234
|
super(connection, logger)
|
57
|
-
@config = config
|
58
|
-
|
59
|
-
|
235
|
+
@connection_parameters, @config = connection_parameters, config
|
236
|
+
|
237
|
+
connect
|
60
238
|
end
|
61
239
|
|
62
240
|
# Is this connection alive and ready for queries?
|
@@ -64,29 +242,32 @@ module ActiveRecord
|
|
64
242
|
if @connection.respond_to?(:status)
|
65
243
|
@connection.status == PGconn::CONNECTION_OK
|
66
244
|
else
|
245
|
+
# We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
|
67
246
|
@connection.query 'SELECT 1'
|
68
247
|
true
|
69
248
|
end
|
70
|
-
# postgres-pr raises a NoMethodError when querying if no
|
249
|
+
# postgres-pr raises a NoMethodError when querying if no connection is available.
|
71
250
|
rescue PGError, NoMethodError
|
72
251
|
false
|
73
252
|
end
|
74
253
|
|
75
254
|
# Close then reopen the connection.
|
76
255
|
def reconnect!
|
77
|
-
# TODO: postgres-pr doesn't have PGconn#reset.
|
78
256
|
if @connection.respond_to?(:reset)
|
79
257
|
@connection.reset
|
80
258
|
configure_connection
|
259
|
+
else
|
260
|
+
disconnect!
|
261
|
+
connect
|
81
262
|
end
|
82
263
|
end
|
83
264
|
|
265
|
+
# Close the connection.
|
84
266
|
def disconnect!
|
85
|
-
# Both postgres and postgres-pr respond to :close
|
86
267
|
@connection.close rescue nil
|
87
268
|
end
|
88
269
|
|
89
|
-
def native_database_types
|
270
|
+
def native_database_types #:nodoc:
|
90
271
|
{
|
91
272
|
:primary_key => "serial primary key",
|
92
273
|
:string => { :name => "character varying", :limit => 255 },
|
@@ -103,41 +284,113 @@ module ActiveRecord
|
|
103
284
|
}
|
104
285
|
end
|
105
286
|
|
287
|
+
# Does PostgreSQL support migrations?
|
106
288
|
def supports_migrations?
|
107
289
|
true
|
108
290
|
end
|
109
291
|
|
292
|
+
# Does PostgreSQL support standard conforming strings?
|
293
|
+
def supports_standard_conforming_strings?
|
294
|
+
# Temporarily set the client message level above error to prevent unintentional
|
295
|
+
# error messages in the logs when working on a PostgreSQL database server that
|
296
|
+
# does not support standard conforming strings.
|
297
|
+
client_min_messages_old = client_min_messages
|
298
|
+
self.client_min_messages = 'panic'
|
299
|
+
|
300
|
+
# postgres-pr does not raise an exception when client_min_messages is set higher
|
301
|
+
# than error and "SHOW standard_conforming_strings" fails, but returns an empty
|
302
|
+
# PGresult instead.
|
303
|
+
has_support = execute('SHOW standard_conforming_strings')[0][0] rescue false
|
304
|
+
self.client_min_messages = client_min_messages_old
|
305
|
+
has_support
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns the configured supported identifier length supported by PostgreSQL,
|
309
|
+
# or report the default of 63 on PostgreSQL 7.x.
|
110
310
|
def table_alias_length
|
111
|
-
63
|
311
|
+
@table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
|
112
312
|
end
|
113
313
|
|
114
314
|
# QUOTING ==================================================
|
115
315
|
|
116
|
-
|
316
|
+
# Quotes PostgreSQL-specific data types for SQL input.
|
317
|
+
def quote(value, column = nil) #:nodoc:
|
117
318
|
if value.kind_of?(String) && column && column.type == :binary
|
118
|
-
"'#{
|
319
|
+
"#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
|
320
|
+
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
321
|
+
"xml '#{quote_string(value)}'"
|
322
|
+
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
323
|
+
# Not truly string input, so doesn't require (or allow) escape string syntax.
|
324
|
+
"'#{value.to_s}'"
|
325
|
+
elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
|
326
|
+
case value
|
327
|
+
when /^[01]*$/
|
328
|
+
"B'#{value}'" # Bit-string notation
|
329
|
+
when /^[0-9A-F]*$/i
|
330
|
+
"X'#{value}'" # Hexadecimal notation
|
331
|
+
end
|
119
332
|
else
|
120
333
|
super
|
121
334
|
end
|
122
335
|
end
|
123
336
|
|
124
|
-
|
337
|
+
# Quotes strings for use in SQL input in the postgres driver for better performance.
|
338
|
+
def quote_string(s) #:nodoc:
|
339
|
+
if PGconn.respond_to?(:escape)
|
340
|
+
self.class.instance_eval do
|
341
|
+
define_method(:quote_string) do |s|
|
342
|
+
PGconn.escape(s)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
else
|
346
|
+
# There are some incorrectly compiled postgres drivers out there
|
347
|
+
# that don't define PGconn.escape.
|
348
|
+
self.class.instance_eval do
|
349
|
+
undef_method(:quote_string)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
quote_string(s)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Quotes column names for use in SQL queries.
|
356
|
+
def quote_column_name(name) #:nodoc:
|
125
357
|
%("#{name}")
|
126
358
|
end
|
127
359
|
|
128
|
-
|
129
|
-
|
360
|
+
# Quote date/time values for use in SQL input. Includes microseconds
|
361
|
+
# if the value is a Time responding to usec.
|
362
|
+
def quoted_date(value) #:nodoc:
|
363
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
364
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
365
|
+
else
|
366
|
+
super
|
367
|
+
end
|
130
368
|
end
|
131
369
|
|
370
|
+
# REFERENTIAL INTEGRITY ====================================
|
371
|
+
|
372
|
+
def disable_referential_integrity(&block) #:nodoc:
|
373
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
374
|
+
yield
|
375
|
+
ensure
|
376
|
+
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
377
|
+
end
|
132
378
|
|
133
379
|
# DATABASE STATEMENTS ======================================
|
134
380
|
|
135
|
-
|
136
|
-
|
381
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
382
|
+
# array of field values.
|
383
|
+
def select_rows(sql, name = nil)
|
384
|
+
select_raw(sql, name).last
|
385
|
+
end
|
386
|
+
|
387
|
+
# Executes an INSERT query and returns the new record's ID
|
388
|
+
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
137
389
|
table = sql.split(" ", 4)[2]
|
138
|
-
|
390
|
+
super || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
|
139
391
|
end
|
140
392
|
|
393
|
+
# Queries the database and returns the results in an Array or nil otherwise.
|
141
394
|
def query(sql, name = nil) #:nodoc:
|
142
395
|
log(sql, name) do
|
143
396
|
if @async
|
@@ -148,7 +401,9 @@ module ActiveRecord
|
|
148
401
|
end
|
149
402
|
end
|
150
403
|
|
151
|
-
|
404
|
+
# Executes an SQL statement, returning a PGresult object on success
|
405
|
+
# or raising a PGError exception otherwise.
|
406
|
+
def execute(sql, name = nil)
|
152
407
|
log(sql, name) do
|
153
408
|
if @async
|
154
409
|
@connection.async_exec(sql)
|
@@ -158,26 +413,30 @@ module ActiveRecord
|
|
158
413
|
end
|
159
414
|
end
|
160
415
|
|
161
|
-
|
162
|
-
|
416
|
+
# Executes an UPDATE query and returns the number of affected tuples.
|
417
|
+
def update_sql(sql, name = nil)
|
418
|
+
super.cmdtuples
|
163
419
|
end
|
164
420
|
|
165
|
-
|
421
|
+
# Begins a transaction.
|
422
|
+
def begin_db_transaction
|
166
423
|
execute "BEGIN"
|
167
424
|
end
|
168
425
|
|
169
|
-
|
426
|
+
# Commits a transaction.
|
427
|
+
def commit_db_transaction
|
170
428
|
execute "COMMIT"
|
171
429
|
end
|
172
430
|
|
173
|
-
|
431
|
+
# Aborts a transaction.
|
432
|
+
def rollback_db_transaction
|
174
433
|
execute "ROLLBACK"
|
175
434
|
end
|
176
435
|
|
177
436
|
# SCHEMA STATEMENTS ========================================
|
178
437
|
|
179
|
-
#
|
180
|
-
def tables(name = nil)
|
438
|
+
# Returns the list of all tables in the schema search path or a specified schema.
|
439
|
+
def tables(name = nil)
|
181
440
|
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
182
441
|
query(<<-SQL, name).map { |row| row[0] }
|
183
442
|
SELECT tablename
|
@@ -186,7 +445,8 @@ module ActiveRecord
|
|
186
445
|
SQL
|
187
446
|
end
|
188
447
|
|
189
|
-
|
448
|
+
# Returns the list of all indexes for a table.
|
449
|
+
def indexes(table_name, name = nil)
|
190
450
|
result = query(<<-SQL, name)
|
191
451
|
SELECT i.relname, d.indisunique, a.attname
|
192
452
|
FROM pg_class t, pg_class i, pg_index d, pg_attribute a
|
@@ -219,34 +479,49 @@ module ActiveRecord
|
|
219
479
|
indexes
|
220
480
|
end
|
221
481
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
482
|
+
# Returns the list of all column definitions for a table.
|
483
|
+
def columns(table_name, name = nil)
|
484
|
+
# Limit, precision, and scale are all handled by the superclass.
|
485
|
+
column_definitions(table_name).collect do |name, type, default, notnull|
|
486
|
+
PostgreSQLColumn.new(name, default, type, notnull == 'f')
|
226
487
|
end
|
227
488
|
end
|
228
489
|
|
229
|
-
#
|
230
|
-
# Names beginning with $
|
231
|
-
# See http://www.postgresql.org/docs/
|
232
|
-
|
490
|
+
# Sets the schema search path to a string of comma-separated schema names.
|
491
|
+
# Names beginning with $ have to be quoted (e.g. $user => '$user').
|
492
|
+
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
|
493
|
+
#
|
494
|
+
# This should be not be called manually but set in database.yml.
|
495
|
+
def schema_search_path=(schema_csv)
|
233
496
|
if schema_csv
|
234
497
|
execute "SET search_path TO #{schema_csv}"
|
235
|
-
@schema_search_path =
|
498
|
+
@schema_search_path = schema_csv
|
236
499
|
end
|
237
500
|
end
|
238
501
|
|
239
|
-
|
502
|
+
# Returns the active schema search path.
|
503
|
+
def schema_search_path
|
240
504
|
@schema_search_path ||= query('SHOW search_path')[0][0]
|
241
505
|
end
|
242
506
|
|
243
|
-
|
507
|
+
# Returns the current client message level.
|
508
|
+
def client_min_messages
|
509
|
+
query('SHOW client_min_messages')[0][0]
|
510
|
+
end
|
511
|
+
|
512
|
+
# Set the client message level.
|
513
|
+
def client_min_messages=(level)
|
514
|
+
execute("SET client_min_messages TO '#{level}'")
|
515
|
+
end
|
516
|
+
|
517
|
+
# Returns the sequence name for a table's primary key or some other specified key.
|
518
|
+
def default_sequence_name(table_name, pk = nil) #:nodoc:
|
244
519
|
default_pk, default_seq = pk_and_sequence_for(table_name)
|
245
520
|
default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
|
246
521
|
end
|
247
522
|
|
248
|
-
# Resets
|
249
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
523
|
+
# Resets the sequence of a table's primary key to the maximum value.
|
524
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
|
250
525
|
unless pk and sequence
|
251
526
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
252
527
|
pk ||= default_pk
|
@@ -263,19 +538,18 @@ module ActiveRecord
|
|
263
538
|
end
|
264
539
|
end
|
265
540
|
|
266
|
-
#
|
267
|
-
def pk_and_sequence_for(table)
|
541
|
+
# Returns a table's primary key and belonging sequence.
|
542
|
+
def pk_and_sequence_for(table) #:nodoc:
|
268
543
|
# First try looking for a sequence with a dependency on the
|
269
544
|
# given table's primary key.
|
270
545
|
result = query(<<-end_sql, 'PK and serial sequence')[0]
|
271
|
-
SELECT attr.attname,
|
546
|
+
SELECT attr.attname, seq.relname
|
272
547
|
FROM pg_class seq,
|
273
548
|
pg_attribute attr,
|
274
549
|
pg_depend dep,
|
275
550
|
pg_namespace name,
|
276
551
|
pg_constraint cons
|
277
552
|
WHERE seq.oid = dep.objid
|
278
|
-
AND seq.relnamespace = name.oid
|
279
553
|
AND seq.relkind = 'S'
|
280
554
|
AND attr.attrelid = dep.refobjid
|
281
555
|
AND attr.attnum = dep.refobjsubid
|
@@ -289,11 +563,9 @@ module ActiveRecord
|
|
289
563
|
# If that fails, try parsing the primary key's default value.
|
290
564
|
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
291
565
|
# the 8.1+ nextval('foo'::regclass).
|
292
|
-
# TODO: assumes sequence is in same schema as table.
|
293
566
|
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
294
|
-
SELECT attr.attname,
|
567
|
+
SELECT attr.attname, split_part(def.adsrc, '''', 2)
|
295
568
|
FROM pg_class t
|
296
|
-
JOIN pg_namespace name ON (t.relnamespace = name.oid)
|
297
569
|
JOIN pg_attribute attr ON (t.oid = attrelid)
|
298
570
|
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
299
571
|
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
@@ -302,68 +574,72 @@ module ActiveRecord
|
|
302
574
|
AND def.adsrc ~* 'nextval'
|
303
575
|
end_sql
|
304
576
|
end
|
305
|
-
#
|
306
|
-
# We cannot qualify unqualified sequences, as rails doesn't qualify any table access, using the search path
|
577
|
+
# [primary_key, sequence]
|
307
578
|
[result.first, result.last]
|
308
579
|
rescue
|
309
580
|
nil
|
310
581
|
end
|
311
582
|
|
583
|
+
# Renames a table.
|
312
584
|
def rename_table(name, new_name)
|
313
585
|
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
314
586
|
end
|
315
587
|
|
588
|
+
# Adds a column to a table.
|
316
589
|
def add_column(table_name, column_name, type, options = {})
|
317
590
|
default = options[:default]
|
318
591
|
notnull = options[:null] == false
|
319
592
|
|
320
593
|
# Add the column.
|
321
|
-
execute("ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit])}")
|
594
|
+
execute("ALTER TABLE #{table_name} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
|
322
595
|
|
323
|
-
|
324
|
-
if
|
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
|
596
|
+
change_column_default(table_name, column_name, default) if options_include_default?(options)
|
597
|
+
change_column_null(table_name, column_name, false, default) if notnull
|
334
598
|
end
|
335
599
|
|
336
|
-
|
600
|
+
# Changes the column of a table.
|
601
|
+
def change_column(table_name, column_name, type, options = {})
|
337
602
|
begin
|
338
|
-
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
603
|
+
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
339
604
|
rescue ActiveRecord::StatementInvalid
|
340
|
-
# This is
|
605
|
+
# This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
|
341
606
|
begin_db_transaction
|
342
|
-
|
343
|
-
|
607
|
+
tmp_column_name = "#{column_name}_ar_tmp"
|
608
|
+
add_column(table_name, tmp_column_name, type, options)
|
609
|
+
execute "UPDATE #{table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
|
344
610
|
remove_column(table_name, column_name)
|
345
|
-
rename_column(table_name,
|
611
|
+
rename_column(table_name, tmp_column_name, column_name)
|
346
612
|
commit_db_transaction
|
347
613
|
end
|
348
614
|
|
349
|
-
if options_include_default?(options)
|
350
|
-
|
351
|
-
end
|
615
|
+
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
616
|
+
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
352
617
|
end
|
353
618
|
|
354
|
-
|
619
|
+
# Changes the default value of a table column.
|
620
|
+
def change_column_default(table_name, column_name, default)
|
355
621
|
execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
|
356
622
|
end
|
357
623
|
|
358
|
-
def
|
624
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
625
|
+
unless null || default.nil?
|
626
|
+
execute("UPDATE #{table_name} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
627
|
+
end
|
628
|
+
execute("ALTER TABLE #{table_name} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
629
|
+
end
|
630
|
+
|
631
|
+
# Renames a column in a table.
|
632
|
+
def rename_column(table_name, column_name, new_column_name)
|
359
633
|
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
|
360
634
|
end
|
361
635
|
|
362
|
-
|
636
|
+
# Drops an index from a table.
|
637
|
+
def remove_index(table_name, options = {})
|
363
638
|
execute "DROP INDEX #{index_name(table_name, options)}"
|
364
639
|
end
|
365
640
|
|
366
|
-
|
641
|
+
# Maps logical Rails types to PostgreSQL-specific data types.
|
642
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
367
643
|
return super unless type.to_s == 'integer'
|
368
644
|
|
369
645
|
if limit.nil? || limit == 4
|
@@ -375,32 +651,32 @@ module ActiveRecord
|
|
375
651
|
end
|
376
652
|
end
|
377
653
|
|
378
|
-
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
654
|
+
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
379
655
|
#
|
380
656
|
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
|
381
657
|
# requires that the ORDER BY include the distinct column.
|
382
658
|
#
|
383
659
|
# distinct("posts.id", "posts.created_at desc")
|
384
|
-
def distinct(columns, order_by)
|
660
|
+
def distinct(columns, order_by) #:nodoc:
|
385
661
|
return "DISTINCT #{columns}" if order_by.blank?
|
386
662
|
|
387
|
-
#
|
388
|
-
# any
|
663
|
+
# Construct a clean list of column names from the ORDER BY clause, removing
|
664
|
+
# any ASC/DESC modifiers
|
389
665
|
order_columns = order_by.split(',').collect { |s| s.split.first }
|
390
666
|
order_columns.delete_if &:blank?
|
391
667
|
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
392
668
|
|
393
|
-
#
|
394
|
-
# all the required columns for the ORDER BY to work properly
|
669
|
+
# Return a DISTINCT ON() clause that's distinct on the columns we want but includes
|
670
|
+
# all the required columns for the ORDER BY to work properly.
|
395
671
|
sql = "DISTINCT ON (#{columns}) #{columns}, "
|
396
672
|
sql << order_columns * ', '
|
397
673
|
end
|
398
674
|
|
399
|
-
# ORDER BY clause for the passed order option.
|
675
|
+
# Returns an ORDER BY clause for the passed order option.
|
400
676
|
#
|
401
677
|
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
402
678
|
# by wrapping the sql as a sub-select and ordering in that query.
|
403
|
-
def add_order_by_for_association_limiting!(sql, options)
|
679
|
+
def add_order_by_for_association_limiting!(sql, options) #:nodoc:
|
404
680
|
return sql if options[:order].blank?
|
405
681
|
|
406
682
|
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
@@ -410,103 +686,135 @@ module ActiveRecord
|
|
410
686
|
sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
|
411
687
|
end
|
412
688
|
|
689
|
+
protected
|
690
|
+
# Returns the version of the connected PostgreSQL version.
|
691
|
+
def postgresql_version
|
692
|
+
@postgresql_version ||=
|
693
|
+
if @connection.respond_to?(:server_version)
|
694
|
+
@connection.server_version
|
695
|
+
else
|
696
|
+
# Mimic PGconn.server_version behavior
|
697
|
+
begin
|
698
|
+
query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
|
699
|
+
($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
|
700
|
+
rescue
|
701
|
+
0
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
413
706
|
private
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
707
|
+
# The internal PostgreSQL identifer of the money data type.
|
708
|
+
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
|
709
|
+
|
710
|
+
# Connects to a PostgreSQL server and sets up the adapter depending on the
|
711
|
+
# connected server's characteristics.
|
712
|
+
def connect
|
713
|
+
@connection = PGconn.connect(*@connection_parameters)
|
714
|
+
PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
|
715
|
+
|
716
|
+
# Ignore async_exec and async_query when using postgres-pr.
|
717
|
+
@async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
|
718
|
+
|
719
|
+
# Use escape string syntax if available. We cannot do this lazily when encountering
|
720
|
+
# the first string, because that could then break any transactions in progress.
|
721
|
+
# See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
722
|
+
# If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
|
723
|
+
# support escape string syntax. Don't override the inherited quoted_string_prefix.
|
724
|
+
if supports_standard_conforming_strings?
|
725
|
+
self.class.instance_eval do
|
726
|
+
define_method(:quoted_string_prefix) { 'E' }
|
727
|
+
end
|
728
|
+
end
|
418
729
|
|
730
|
+
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
|
731
|
+
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
|
732
|
+
# should know about this but can't detect it there, so deal with it here.
|
733
|
+
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
734
|
+
PostgreSQLColumn.module_eval(<<-end_eval)
|
735
|
+
def extract_precision(sql_type)
|
736
|
+
if sql_type =~ /^money$/
|
737
|
+
#{money_precision}
|
738
|
+
else
|
739
|
+
super
|
740
|
+
end
|
741
|
+
end
|
742
|
+
end_eval
|
743
|
+
|
744
|
+
configure_connection
|
745
|
+
end
|
746
|
+
|
747
|
+
# Configures the encoding, verbosity, and schema search path of the connection.
|
748
|
+
# This is called by #connect and should not be called manually.
|
419
749
|
def configure_connection
|
420
750
|
if @config[:encoding]
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
751
|
+
if @connection.respond_to?(:set_client_encoding)
|
752
|
+
@connection.set_client_encoding(@config[:encoding])
|
753
|
+
else
|
754
|
+
execute("SET client_encoding TO '#{@config[:encoding]}'")
|
755
|
+
end
|
425
756
|
end
|
757
|
+
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
|
758
|
+
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
426
759
|
end
|
427
760
|
|
428
|
-
|
761
|
+
# Returns the current ID of a table's sequence.
|
762
|
+
def last_insert_id(table, sequence_name) #:nodoc:
|
429
763
|
Integer(select_value("SELECT currval('#{sequence_name}')"))
|
430
764
|
end
|
431
765
|
|
766
|
+
# Executes a SELECT query and returns the results, performing any data type
|
767
|
+
# conversions that are required to be performed here instead of in PostgreSQLColumn.
|
432
768
|
def select(sql, name = nil)
|
769
|
+
fields, rows = select_raw(sql, name)
|
770
|
+
result = []
|
771
|
+
for row in rows
|
772
|
+
row_hash = {}
|
773
|
+
fields.each_with_index do |f, i|
|
774
|
+
row_hash[f] = row[i]
|
775
|
+
end
|
776
|
+
result << row_hash
|
777
|
+
end
|
778
|
+
result
|
779
|
+
end
|
780
|
+
|
781
|
+
def select_raw(sql, name = nil)
|
433
782
|
res = execute(sql, name)
|
434
783
|
results = res.result
|
784
|
+
fields = []
|
435
785
|
rows = []
|
436
786
|
if results.length > 0
|
437
787
|
fields = res.fields
|
438
788
|
results.each do |row|
|
439
789
|
hashed_row = {}
|
440
|
-
row.each_index do |
|
441
|
-
column
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
790
|
+
row.each_index do |cell_index|
|
791
|
+
# If this is a money type column and there are any currency symbols,
|
792
|
+
# then strip them off. Indeed it would be prettier to do this in
|
793
|
+
# PostgreSQLColumn.string_to_decimal but would break form input
|
794
|
+
# fields that call value_before_type_cast.
|
795
|
+
if res.type(cell_index) == MONEY_COLUMN_TYPE_OID
|
796
|
+
# Because money output is formatted according to the locale, there are two
|
797
|
+
# cases to consider (note the decimal separators):
|
798
|
+
# (1) $12,345,678.12
|
799
|
+
# (2) $12.345.678,12
|
800
|
+
case column = row[cell_index]
|
801
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
802
|
+
row[cell_index] = column.gsub(/[^-\d\.]/, '')
|
803
|
+
when /^-?\D+[\d\.]+,\d{2}$/ # (2)
|
804
|
+
row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
|
805
|
+
end
|
450
806
|
end
|
451
807
|
|
452
|
-
hashed_row[fields[
|
808
|
+
hashed_row[fields[cell_index]] = column
|
453
809
|
end
|
454
|
-
rows <<
|
810
|
+
rows << row
|
455
811
|
end
|
456
812
|
end
|
457
813
|
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)
|
814
|
+
return fields, rows
|
507
815
|
end
|
508
816
|
|
509
|
-
#
|
817
|
+
# Returns the list of a table's column names, data types, and default values.
|
510
818
|
#
|
511
819
|
# The underlying query is roughly:
|
512
820
|
# SELECT column.name, column.type, default.value
|
@@ -524,7 +832,7 @@ module ActiveRecord
|
|
524
832
|
# Query implementation notes:
|
525
833
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
526
834
|
# - ::regclass is a function that gives the id for a table name
|
527
|
-
def column_definitions(table_name)
|
835
|
+
def column_definitions(table_name) #:nodoc:
|
528
836
|
query <<-end_sql
|
529
837
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
|
530
838
|
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
@@ -534,51 +842,6 @@ module ActiveRecord
|
|
534
842
|
ORDER BY a.attnum
|
535
843
|
end_sql
|
536
844
|
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
845
|
end
|
583
846
|
end
|
584
847
|
end
|