activerecord 1.0.0 → 4.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +213 -0
- data/examples/performance.rb +172 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +180 -84
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +92 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
- data/lib/active_record/associations/has_many_association.rb +116 -85
- data/lib/active_record/associations/has_many_through_association.rb +197 -0
- data/lib/active_record/associations/has_one_association.rb +102 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +1437 -431
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
- data/lib/active_record/attribute_methods/dirty.rb +118 -0
- data/lib/active_record/attribute_methods/primary_key.rb +122 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +107 -0
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
- data/lib/active_record/attribute_methods/write.rb +63 -0
- data/lib/active_record/attribute_methods.rb +393 -0
- data/lib/active_record/autosave_association.rb +426 -0
- data/lib/active_record/base.rb +268 -930
- data/lib/active_record/callbacks.rb +203 -230
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +122 -0
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +213 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +892 -138
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +181 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +82 -0
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +1015 -0
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +546 -0
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +509 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +205 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +402 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +544 -87
- data/lib/active_record/relation/batches.rb +93 -0
- data/lib/active_record/relation/calculations.rb +399 -0
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +349 -0
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +106 -0
- data/lib/active_record/relation/query_methods.rb +1044 -0
- data/lib/active_record/relation/spawn_methods.rb +73 -0
- data/lib/active_record/relation.rb +655 -0
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +65 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +197 -0
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +96 -0
- data/lib/active_record/timestamp.rb +119 -0
- data/lib/active_record/transactions.rb +366 -69
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +49 -0
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +225 -0
- data/lib/active_record/validations.rb +64 -185
- data/lib/active_record/version.rb +11 -0
- data/lib/active_record.rb +149 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record.rb +23 -0
- metadata +261 -161
- data/CHANGELOG +0 -581
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.png +0 -0
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/associations/association_collection.rb +0 -70
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/observer.rb +0 -71
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -0,0 +1,97 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class PostgreSQLColumn < Column
|
4
|
+
module ArrayParser
|
5
|
+
private
|
6
|
+
# Loads pg_array_parser if available. String parsing can be
|
7
|
+
# performed quicker by a native extension, which will not create
|
8
|
+
# a large amount of Ruby objects that will need to be garbage
|
9
|
+
# collected. pg_array_parser has a C and Java extension
|
10
|
+
begin
|
11
|
+
require 'pg_array_parser'
|
12
|
+
include PgArrayParser
|
13
|
+
rescue LoadError
|
14
|
+
def parse_pg_array(string)
|
15
|
+
parse_data(string, 0)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_data(string, index)
|
20
|
+
local_index = index
|
21
|
+
array = []
|
22
|
+
while(local_index < string.length)
|
23
|
+
case string[local_index]
|
24
|
+
when '{'
|
25
|
+
local_index,array = parse_array_contents(array, string, local_index + 1)
|
26
|
+
when '}'
|
27
|
+
return array
|
28
|
+
end
|
29
|
+
local_index += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
array
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_array_contents(array, string, index)
|
36
|
+
is_escaping = false
|
37
|
+
is_quoted = false
|
38
|
+
was_quoted = false
|
39
|
+
current_item = ''
|
40
|
+
|
41
|
+
local_index = index
|
42
|
+
while local_index
|
43
|
+
token = string[local_index]
|
44
|
+
if is_escaping
|
45
|
+
current_item << token
|
46
|
+
is_escaping = false
|
47
|
+
else
|
48
|
+
if is_quoted
|
49
|
+
case token
|
50
|
+
when '"'
|
51
|
+
is_quoted = false
|
52
|
+
was_quoted = true
|
53
|
+
when "\\"
|
54
|
+
is_escaping = true
|
55
|
+
else
|
56
|
+
current_item << token
|
57
|
+
end
|
58
|
+
else
|
59
|
+
case token
|
60
|
+
when "\\"
|
61
|
+
is_escaping = true
|
62
|
+
when ','
|
63
|
+
add_item_to_array(array, current_item, was_quoted)
|
64
|
+
current_item = ''
|
65
|
+
was_quoted = false
|
66
|
+
when '"'
|
67
|
+
is_quoted = true
|
68
|
+
when '{'
|
69
|
+
internal_items = []
|
70
|
+
local_index,internal_items = parse_array_contents(internal_items, string, local_index + 1)
|
71
|
+
array.push(internal_items)
|
72
|
+
when '}'
|
73
|
+
add_item_to_array(array, current_item, was_quoted)
|
74
|
+
return local_index,array
|
75
|
+
else
|
76
|
+
current_item << token
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
local_index += 1
|
82
|
+
end
|
83
|
+
return local_index,array
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_item_to_array(array, current_item, quoted)
|
87
|
+
if current_item.length == 0
|
88
|
+
elsif !quoted && current_item == 'NULL'
|
89
|
+
array.push nil
|
90
|
+
else
|
91
|
+
array.push current_item
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class PostgreSQLColumn < Column
|
4
|
+
module Cast
|
5
|
+
def point_to_string(point)
|
6
|
+
"(#{point[0]},#{point[1]})"
|
7
|
+
end
|
8
|
+
|
9
|
+
def string_to_point(string)
|
10
|
+
if string[0] == '(' && string[-1] == ')'
|
11
|
+
string = string[1...-1]
|
12
|
+
end
|
13
|
+
string.split(',').map{ |v| Float(v) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def string_to_time(string)
|
17
|
+
return string unless String === string
|
18
|
+
|
19
|
+
case string
|
20
|
+
when 'infinity'; 1.0 / 0.0
|
21
|
+
when '-infinity'; -1.0 / 0.0
|
22
|
+
when / BC$/
|
23
|
+
super("-" + string.sub(/ BC$/, ""))
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def string_to_bit(value)
|
30
|
+
case value
|
31
|
+
when /^0x/i
|
32
|
+
value[2..-1].hex.to_s(2) # Hexadecimal notation
|
33
|
+
else
|
34
|
+
value # Bit-string notation
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def hstore_to_string(object)
|
39
|
+
if Hash === object
|
40
|
+
object.map { |k,v|
|
41
|
+
"#{escape_hstore(k)}=>#{escape_hstore(v)}"
|
42
|
+
}.join ','
|
43
|
+
else
|
44
|
+
object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def string_to_hstore(string)
|
49
|
+
if string.nil?
|
50
|
+
nil
|
51
|
+
elsif String === string
|
52
|
+
Hash[string.scan(HstorePair).map { |k,v|
|
53
|
+
v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
|
54
|
+
k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
|
55
|
+
[k,v]
|
56
|
+
}]
|
57
|
+
else
|
58
|
+
string
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def json_to_string(object)
|
63
|
+
if Hash === object
|
64
|
+
ActiveSupport::JSON.encode(object)
|
65
|
+
else
|
66
|
+
object
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def array_to_string(value, column, adapter, should_be_quoted = false)
|
71
|
+
casted_values = value.map do |val|
|
72
|
+
if String === val
|
73
|
+
if val == "NULL"
|
74
|
+
"\"#{val}\""
|
75
|
+
else
|
76
|
+
quote_and_escape(adapter.type_cast(val, column, true))
|
77
|
+
end
|
78
|
+
else
|
79
|
+
adapter.type_cast(val, column, true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
"{#{casted_values.join(',')}}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def range_to_string(object)
|
86
|
+
from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
|
87
|
+
to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
|
88
|
+
"[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def string_to_json(string)
|
92
|
+
if String === string
|
93
|
+
ActiveSupport::JSON.decode(string)
|
94
|
+
else
|
95
|
+
string
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def string_to_cidr(string)
|
100
|
+
if string.nil?
|
101
|
+
nil
|
102
|
+
elsif String === string
|
103
|
+
IPAddr.new(string)
|
104
|
+
else
|
105
|
+
string
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def cidr_to_string(object)
|
110
|
+
if IPAddr === object
|
111
|
+
"#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
|
112
|
+
else
|
113
|
+
object
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def string_to_array(string, oid)
|
118
|
+
parse_pg_array(string).map{|val| oid.type_cast val}
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
HstorePair = begin
|
124
|
+
quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
|
125
|
+
unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
|
126
|
+
/(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
|
127
|
+
end
|
128
|
+
|
129
|
+
def escape_hstore(value)
|
130
|
+
if value.nil?
|
131
|
+
'NULL'
|
132
|
+
else
|
133
|
+
if value == ""
|
134
|
+
'""'
|
135
|
+
else
|
136
|
+
'"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def quote_and_escape(value)
|
142
|
+
case value
|
143
|
+
when "NULL"
|
144
|
+
value
|
145
|
+
else
|
146
|
+
"\"#{value.gsub(/"/,"\\\"")}\""
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class PostgreSQLAdapter < AbstractAdapter
|
4
|
+
module DatabaseStatements
|
5
|
+
def explain(arel, binds = [])
|
6
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
7
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
8
|
+
end
|
9
|
+
|
10
|
+
class ExplainPrettyPrinter # :nodoc:
|
11
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
12
|
+
# PostgreSQL shell:
|
13
|
+
#
|
14
|
+
# QUERY PLAN
|
15
|
+
# ------------------------------------------------------------------------------
|
16
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
17
|
+
# Join Filter: (posts.user_id = users.id)
|
18
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
19
|
+
# Index Cond: (id = 1)
|
20
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
21
|
+
# Filter: (posts.user_id = 1)
|
22
|
+
# (6 rows)
|
23
|
+
#
|
24
|
+
def pp(result)
|
25
|
+
header = result.columns.first
|
26
|
+
lines = result.rows.map(&:first)
|
27
|
+
|
28
|
+
# We add 2 because there's one char of padding at both sides, note
|
29
|
+
# the extra hyphens in the example above.
|
30
|
+
width = [header, *lines].map(&:length).max + 2
|
31
|
+
|
32
|
+
pp = []
|
33
|
+
|
34
|
+
pp << header.center(width).rstrip
|
35
|
+
pp << '-' * width
|
36
|
+
|
37
|
+
pp += lines.map {|line| " #{line}"}
|
38
|
+
|
39
|
+
nrows = result.rows.length
|
40
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
41
|
+
pp << "(#{nrows} #{rows_label})"
|
42
|
+
|
43
|
+
pp.join("\n") + "\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
48
|
+
# array of field values.
|
49
|
+
def select_rows(sql, name = nil)
|
50
|
+
select_raw(sql, name).last
|
51
|
+
end
|
52
|
+
|
53
|
+
# Executes an INSERT query and returns the new record's ID
|
54
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
55
|
+
unless pk
|
56
|
+
# Extract the table from the insert sql. Yuck.
|
57
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
58
|
+
pk = primary_key(table_ref) if table_ref
|
59
|
+
end
|
60
|
+
|
61
|
+
if pk && use_insert_returning?
|
62
|
+
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
63
|
+
elsif pk
|
64
|
+
super
|
65
|
+
last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
|
66
|
+
else
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def create
|
72
|
+
super.insert
|
73
|
+
end
|
74
|
+
|
75
|
+
# create a 2D array representing the result set
|
76
|
+
def result_as_array(res) #:nodoc:
|
77
|
+
# check if we have any binary column and if they need escaping
|
78
|
+
ftypes = Array.new(res.nfields) do |i|
|
79
|
+
[i, res.ftype(i)]
|
80
|
+
end
|
81
|
+
|
82
|
+
rows = res.values
|
83
|
+
return rows unless ftypes.any? { |_, x|
|
84
|
+
x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
|
85
|
+
}
|
86
|
+
|
87
|
+
typehash = ftypes.group_by { |_, type| type }
|
88
|
+
binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
|
89
|
+
monies = typehash[MONEY_COLUMN_TYPE_OID] || []
|
90
|
+
|
91
|
+
rows.each do |row|
|
92
|
+
# unescape string passed BYTEA field (OID == 17)
|
93
|
+
binaries.each do |index, _|
|
94
|
+
row[index] = unescape_bytea(row[index])
|
95
|
+
end
|
96
|
+
|
97
|
+
# If this is a money type column and there are any currency symbols,
|
98
|
+
# then strip them off. Indeed it would be prettier to do this in
|
99
|
+
# PostgreSQLColumn.string_to_decimal but would break form input
|
100
|
+
# fields that call value_before_type_cast.
|
101
|
+
monies.each do |index, _|
|
102
|
+
data = row[index]
|
103
|
+
# Because money output is formatted according to the locale, there are two
|
104
|
+
# cases to consider (note the decimal separators):
|
105
|
+
# (1) $12,345,678.12
|
106
|
+
# (2) $12.345.678,12
|
107
|
+
case data
|
108
|
+
when /^-?\D+[\d,]+\.\d{2}$/ # (1)
|
109
|
+
data.gsub!(/[^-\d.]/, '')
|
110
|
+
when /^-?\D+[\d.]+,\d{2}$/ # (2)
|
111
|
+
data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Queries the database and returns the results in an Array-like object
|
118
|
+
def query(sql, name = nil) #:nodoc:
|
119
|
+
log(sql, name) do
|
120
|
+
result_as_array @connection.async_exec(sql)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Executes an SQL statement, returning a PGresult object on success
|
125
|
+
# or raising a PGError exception otherwise.
|
126
|
+
def execute(sql, name = nil)
|
127
|
+
log(sql, name) do
|
128
|
+
@connection.async_exec(sql)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def substitute_at(column, index)
|
133
|
+
Arel::Nodes::BindParam.new "$#{index + 1}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
137
|
+
log(sql, name, binds) do
|
138
|
+
result = binds.empty? ? exec_no_cache(sql, binds) :
|
139
|
+
exec_cache(sql, binds)
|
140
|
+
|
141
|
+
types = {}
|
142
|
+
result.fields.each_with_index do |fname, i|
|
143
|
+
ftype = result.ftype i
|
144
|
+
fmod = result.fmod i
|
145
|
+
types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
|
146
|
+
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
|
147
|
+
OID::Identity.new
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
ret = ActiveRecord::Result.new(result.fields, result.values, types)
|
152
|
+
result.clear
|
153
|
+
return ret
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def exec_delete(sql, name = 'SQL', binds = [])
|
158
|
+
log(sql, name, binds) do
|
159
|
+
result = binds.empty? ? exec_no_cache(sql, binds) :
|
160
|
+
exec_cache(sql, binds)
|
161
|
+
affected = result.cmd_tuples
|
162
|
+
result.clear
|
163
|
+
affected
|
164
|
+
end
|
165
|
+
end
|
166
|
+
alias :exec_update :exec_delete
|
167
|
+
|
168
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
169
|
+
unless pk
|
170
|
+
# Extract the table from the insert sql. Yuck.
|
171
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
172
|
+
pk = primary_key(table_ref) if table_ref
|
173
|
+
end
|
174
|
+
|
175
|
+
if pk && use_insert_returning?
|
176
|
+
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
|
177
|
+
end
|
178
|
+
|
179
|
+
[sql, binds]
|
180
|
+
end
|
181
|
+
|
182
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
183
|
+
val = exec_query(sql, name, binds)
|
184
|
+
if !use_insert_returning? && pk
|
185
|
+
unless sequence_name
|
186
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
187
|
+
sequence_name = default_sequence_name(table_ref, pk)
|
188
|
+
return val unless sequence_name
|
189
|
+
end
|
190
|
+
last_insert_id_result(sequence_name)
|
191
|
+
else
|
192
|
+
val
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Executes an UPDATE query and returns the number of affected tuples.
|
197
|
+
def update_sql(sql, name = nil)
|
198
|
+
super.cmd_tuples
|
199
|
+
end
|
200
|
+
|
201
|
+
# Begins a transaction.
|
202
|
+
def begin_db_transaction
|
203
|
+
execute "BEGIN"
|
204
|
+
end
|
205
|
+
|
206
|
+
def begin_isolated_db_transaction(isolation)
|
207
|
+
begin_db_transaction
|
208
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
209
|
+
end
|
210
|
+
|
211
|
+
# Commits a transaction.
|
212
|
+
def commit_db_transaction
|
213
|
+
execute "COMMIT"
|
214
|
+
end
|
215
|
+
|
216
|
+
# Aborts a transaction.
|
217
|
+
def rollback_db_transaction
|
218
|
+
execute "ROLLBACK"
|
219
|
+
end
|
220
|
+
|
221
|
+
def outside_transaction?
|
222
|
+
message = "#outside_transaction? is deprecated. This method was only really used " \
|
223
|
+
"internally, but you can use #transaction_open? instead."
|
224
|
+
ActiveSupport::Deprecation.warn message
|
225
|
+
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
226
|
+
end
|
227
|
+
|
228
|
+
def create_savepoint
|
229
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
230
|
+
end
|
231
|
+
|
232
|
+
def rollback_to_savepoint
|
233
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
234
|
+
end
|
235
|
+
|
236
|
+
def release_savepoint
|
237
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|