activerecord 3.1.12 → 3.2.22.1
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 +4 -4
- data/CHANGELOG.md +804 -338
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +13 -45
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -4
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +65 -32
- data/lib/active_record/associations/collection_proxy.rb +8 -41
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +11 -7
- data/lib/active_record/associations/has_many_through_association.rb +19 -9
- data/lib/active_record/associations/has_one_association.rb +23 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
- data/lib/active_record/associations/join_dependency.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +3 -3
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +8 -4
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods/write.rb +32 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +44 -26
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +57 -86
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locking/optimistic.rb +33 -26
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +8 -4
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +60 -24
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/query_cache.rb +6 -6
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +37 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +213 -117
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +7 -4
- data/lib/active_record/relation/calculations.rb +55 -16
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +16 -11
- data/lib/active_record/relation/predicate_builder.rb +8 -6
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +48 -7
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +10 -4
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +18 -16
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +3 -3
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +49 -28
- data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'active_support/core_ext/kernel/requires'
|
3
2
|
require 'active_support/core_ext/object/blank'
|
4
3
|
require 'active_record/connection_adapters/statement_pool'
|
5
4
|
require 'arel/visitors/bind_visitor'
|
@@ -143,14 +142,11 @@ module ActiveRecord
|
|
143
142
|
when NilClass
|
144
143
|
nil
|
145
144
|
# Numeric types
|
146
|
-
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
|
145
|
+
when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
|
147
146
|
$1
|
148
147
|
# Character types
|
149
|
-
when /\A'(.*)'
|
148
|
+
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
150
149
|
$1
|
151
|
-
# Character types (8.1 formatting)
|
152
|
-
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
153
|
-
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
154
150
|
# Binary data types
|
155
151
|
when /\A'(.*)'::bytea\z/m
|
156
152
|
$1
|
@@ -201,7 +197,7 @@ module ActiveRecord
|
|
201
197
|
# * <tt>:password</tt> - Defaults to nothing.
|
202
198
|
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
203
199
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
204
|
-
# as a string of comma-separated schema names.
|
200
|
+
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
205
201
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
206
202
|
# <encoding></tt> call on the connection.
|
207
203
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
@@ -249,6 +245,10 @@ module ActiveRecord
|
|
249
245
|
true
|
250
246
|
end
|
251
247
|
|
248
|
+
def supports_index_sort_order?
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
252
252
|
class StatementPool < ConnectionAdapters::StatementPool
|
253
253
|
def initialize(connection, max)
|
254
254
|
super
|
@@ -309,7 +309,11 @@ module ActiveRecord
|
|
309
309
|
def initialize(connection, logger, connection_parameters, config)
|
310
310
|
super(connection, logger)
|
311
311
|
|
312
|
-
|
312
|
+
if config.fetch(:prepared_statements) { true }
|
313
|
+
@visitor = Arel::Visitors::PostgreSQL.new self
|
314
|
+
else
|
315
|
+
@visitor = BindSubstitution.new self
|
316
|
+
end
|
313
317
|
|
314
318
|
@connection_parameters, @config = connection_parameters, config
|
315
319
|
|
@@ -328,15 +332,6 @@ module ActiveRecord
|
|
328
332
|
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
|
329
333
|
end
|
330
334
|
|
331
|
-
def self.visitor_for(pool) # :nodoc:
|
332
|
-
config = pool.spec.config
|
333
|
-
if config.fetch(:prepared_statements) { true }
|
334
|
-
Arel::Visitors::PostgreSQL.new pool
|
335
|
-
else
|
336
|
-
BindSubstitution.new pool
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
335
|
# Clears the prepared statements cache.
|
341
336
|
def clear_cache!
|
342
337
|
@statements.clear
|
@@ -344,7 +339,8 @@ module ActiveRecord
|
|
344
339
|
|
345
340
|
# Is this connection alive and ready for queries?
|
346
341
|
def active?
|
347
|
-
@connection.
|
342
|
+
@connection.query 'SELECT 1'
|
343
|
+
true
|
348
344
|
rescue PGError
|
349
345
|
false
|
350
346
|
end
|
@@ -353,6 +349,7 @@ module ActiveRecord
|
|
353
349
|
def reconnect!
|
354
350
|
clear_cache!
|
355
351
|
@connection.reset
|
352
|
+
@open_transactions = 0
|
356
353
|
configure_connection
|
357
354
|
end
|
358
355
|
|
@@ -403,6 +400,11 @@ module ActiveRecord
|
|
403
400
|
true
|
404
401
|
end
|
405
402
|
|
403
|
+
# Returns true.
|
404
|
+
def supports_explain?
|
405
|
+
true
|
406
|
+
end
|
407
|
+
|
406
408
|
# Returns the configured supported identifier length supported by PostgreSQL
|
407
409
|
def table_alias_length
|
408
410
|
@table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
|
@@ -440,8 +442,8 @@ module ActiveRecord
|
|
440
442
|
when 'xml' then "xml '#{quote_string(value)}'"
|
441
443
|
when /^bit/
|
442
444
|
case value
|
443
|
-
when
|
444
|
-
when
|
445
|
+
when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
|
446
|
+
when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
|
445
447
|
end
|
446
448
|
else
|
447
449
|
super
|
@@ -527,6 +529,48 @@ module ActiveRecord
|
|
527
529
|
|
528
530
|
# DATABASE STATEMENTS ======================================
|
529
531
|
|
532
|
+
def explain(arel, binds = [])
|
533
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
534
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
535
|
+
end
|
536
|
+
|
537
|
+
class ExplainPrettyPrinter # :nodoc:
|
538
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
539
|
+
# PostgreSQL shell:
|
540
|
+
#
|
541
|
+
# QUERY PLAN
|
542
|
+
# ------------------------------------------------------------------------------
|
543
|
+
# Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
|
544
|
+
# Join Filter: (posts.user_id = users.id)
|
545
|
+
# -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
|
546
|
+
# Index Cond: (id = 1)
|
547
|
+
# -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
|
548
|
+
# Filter: (posts.user_id = 1)
|
549
|
+
# (6 rows)
|
550
|
+
#
|
551
|
+
def pp(result)
|
552
|
+
header = result.columns.first
|
553
|
+
lines = result.rows.map(&:first)
|
554
|
+
|
555
|
+
# We add 2 because there's one char of padding at both sides, note
|
556
|
+
# the extra hyphens in the example above.
|
557
|
+
width = [header, *lines].map(&:length).max + 2
|
558
|
+
|
559
|
+
pp = []
|
560
|
+
|
561
|
+
pp << header.center(width).rstrip
|
562
|
+
pp << '-' * width
|
563
|
+
|
564
|
+
pp += lines.map {|line| " #{line}"}
|
565
|
+
|
566
|
+
nrows = result.rows.length
|
567
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
568
|
+
pp << "(#{nrows} #{rows_label})"
|
569
|
+
|
570
|
+
pp.join("\n") + "\n"
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
530
574
|
# Executes a SELECT query and returns an array of rows. Each row is an
|
531
575
|
# array of field values.
|
532
576
|
def select_rows(sql, name = nil)
|
@@ -535,10 +579,11 @@ module ActiveRecord
|
|
535
579
|
|
536
580
|
# Executes an INSERT query and returns the new record's ID
|
537
581
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
582
|
+
unless pk
|
583
|
+
# Extract the table from the insert sql. Yuck.
|
584
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
585
|
+
pk = primary_key(table_ref) if table_ref
|
586
|
+
end
|
542
587
|
|
543
588
|
if pk
|
544
589
|
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
|
@@ -634,9 +679,9 @@ module ActiveRecord
|
|
634
679
|
|
635
680
|
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
636
681
|
unless pk
|
637
|
-
|
638
|
-
|
639
|
-
pk = primary_key(
|
682
|
+
# Extract the table from the insert sql. Yuck.
|
683
|
+
table_ref = extract_table_ref_from_insert_sql(sql)
|
684
|
+
pk = primary_key(table_ref) if table_ref
|
640
685
|
end
|
641
686
|
|
642
687
|
sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
|
@@ -682,12 +727,14 @@ module ActiveRecord
|
|
682
727
|
|
683
728
|
# SCHEMA STATEMENTS ========================================
|
684
729
|
|
685
|
-
|
730
|
+
# Drops the database specified on the +name+ attribute
|
731
|
+
# and creates it again using the provided +options+.
|
732
|
+
def recreate_database(name, options = {}) #:nodoc:
|
686
733
|
drop_database(name)
|
687
|
-
create_database(name)
|
734
|
+
create_database(name, options)
|
688
735
|
end
|
689
736
|
|
690
|
-
# Create a new PostgreSQL database.
|
737
|
+
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
|
691
738
|
# <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
|
692
739
|
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
|
693
740
|
#
|
@@ -734,41 +781,39 @@ module ActiveRecord
|
|
734
781
|
SQL
|
735
782
|
end
|
736
783
|
|
784
|
+
# Returns true if table exists.
|
785
|
+
# If the schema is not specified as part of +name+ then it will only find tables within
|
786
|
+
# the current schema search path (regardless of permissions to access tables in other schemas)
|
737
787
|
def table_exists?(name)
|
738
|
-
schema, table = extract_schema_and_table(name.to_s)
|
739
|
-
return false unless table
|
788
|
+
schema, table = Utils.extract_schema_and_table(name.to_s)
|
789
|
+
return false unless table
|
740
790
|
|
741
|
-
binds = [[nil, table
|
791
|
+
binds = [[nil, table]]
|
742
792
|
binds << [nil, schema] if schema
|
743
793
|
|
744
|
-
exec_query(<<-SQL, 'SCHEMA'
|
794
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
745
795
|
SELECT COUNT(*)
|
746
|
-
FROM
|
747
|
-
|
748
|
-
|
796
|
+
FROM pg_class c
|
797
|
+
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
798
|
+
WHERE c.relkind in ('v','r')
|
799
|
+
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
|
800
|
+
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
|
749
801
|
SQL
|
750
802
|
end
|
751
803
|
|
752
|
-
#
|
753
|
-
def
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
end
|
760
|
-
|
761
|
-
if name =~ /^"/ # Handle quoted table names
|
762
|
-
table = name
|
763
|
-
schema = nil
|
764
|
-
end
|
765
|
-
[schema, table]
|
804
|
+
# Returns true if schema exists.
|
805
|
+
def schema_exists?(name)
|
806
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
807
|
+
SELECT COUNT(*)
|
808
|
+
FROM pg_namespace
|
809
|
+
WHERE nspname = '#{name}'
|
810
|
+
SQL
|
766
811
|
end
|
767
812
|
|
768
813
|
# Returns an array of indexes for the given table.
|
769
814
|
def indexes(table_name, name = nil)
|
770
|
-
result = query(<<-SQL,
|
771
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
|
815
|
+
result = query(<<-SQL, 'SCHEMA')
|
816
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
772
817
|
FROM pg_class t
|
773
818
|
INNER JOIN pg_index d ON t.oid = d.indrelid
|
774
819
|
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
@@ -784,9 +829,10 @@ module ActiveRecord
|
|
784
829
|
index_name = row[0]
|
785
830
|
unique = row[1] == 't'
|
786
831
|
indkey = row[2].split(" ")
|
787
|
-
|
832
|
+
inddef = row[3]
|
833
|
+
oid = row[4]
|
788
834
|
|
789
|
-
columns = Hash[query(<<-SQL, "
|
835
|
+
columns = Hash[query(<<-SQL, "SCHEMA")]
|
790
836
|
SELECT a.attnum, a.attname
|
791
837
|
FROM pg_attribute a
|
792
838
|
WHERE a.attrelid = #{oid}
|
@@ -794,7 +840,12 @@ module ActiveRecord
|
|
794
840
|
SQL
|
795
841
|
|
796
842
|
column_names = columns.values_at(*indkey).compact
|
797
|
-
|
843
|
+
|
844
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
845
|
+
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
846
|
+
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
|
847
|
+
|
848
|
+
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
798
849
|
end.compact
|
799
850
|
end
|
800
851
|
|
@@ -808,12 +859,17 @@ module ActiveRecord
|
|
808
859
|
|
809
860
|
# Returns the current database name.
|
810
861
|
def current_database
|
811
|
-
query('select current_database()')[0][0]
|
862
|
+
query('select current_database()', 'SCHEMA')[0][0]
|
863
|
+
end
|
864
|
+
|
865
|
+
# Returns the current schema name.
|
866
|
+
def current_schema
|
867
|
+
query('SELECT current_schema', 'SCHEMA')[0][0]
|
812
868
|
end
|
813
869
|
|
814
870
|
# Returns the current database encoding format.
|
815
871
|
def encoding
|
816
|
-
query(<<-end_sql)[0][0]
|
872
|
+
query(<<-end_sql, 'SCHEMA')[0][0]
|
817
873
|
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
|
818
874
|
WHERE pg_database.datname LIKE '#{current_database}'
|
819
875
|
end_sql
|
@@ -826,14 +882,14 @@ module ActiveRecord
|
|
826
882
|
# This should be not be called manually but set in database.yml.
|
827
883
|
def schema_search_path=(schema_csv)
|
828
884
|
if schema_csv
|
829
|
-
execute
|
885
|
+
execute("SET search_path TO #{schema_csv}", 'SCHEMA')
|
830
886
|
@schema_search_path = schema_csv
|
831
887
|
end
|
832
888
|
end
|
833
889
|
|
834
890
|
# Returns the active schema search path.
|
835
891
|
def schema_search_path
|
836
|
-
@schema_search_path ||= query('SHOW search_path')[0][0]
|
892
|
+
@schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
|
837
893
|
end
|
838
894
|
|
839
895
|
# Returns the current client message level.
|
@@ -854,8 +910,8 @@ module ActiveRecord
|
|
854
910
|
end
|
855
911
|
|
856
912
|
def serial_sequence(table, column)
|
857
|
-
result = exec_query(<<-eosql, 'SCHEMA'
|
858
|
-
SELECT pg_get_serial_sequence(
|
913
|
+
result = exec_query(<<-eosql, 'SCHEMA')
|
914
|
+
SELECT pg_get_serial_sequence('#{table}', '#{column}')
|
859
915
|
eosql
|
860
916
|
result.rows.first.first
|
861
917
|
end
|
@@ -874,9 +930,9 @@ module ActiveRecord
|
|
874
930
|
end
|
875
931
|
|
876
932
|
if pk && sequence
|
877
|
-
quoted_sequence =
|
933
|
+
quoted_sequence = quote_table_name(sequence)
|
878
934
|
|
879
|
-
select_value <<-end_sql, '
|
935
|
+
select_value <<-end_sql, 'SCHEMA'
|
880
936
|
SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
|
881
937
|
end_sql
|
882
938
|
end
|
@@ -886,7 +942,7 @@ module ActiveRecord
|
|
886
942
|
def pk_and_sequence_for(table) #:nodoc:
|
887
943
|
# First try looking for a sequence with a dependency on the
|
888
944
|
# given table's primary key.
|
889
|
-
result = query(<<-end_sql, '
|
945
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
890
946
|
SELECT attr.attname, seq.relname
|
891
947
|
FROM pg_class seq,
|
892
948
|
pg_attribute attr,
|
@@ -904,16 +960,13 @@ module ActiveRecord
|
|
904
960
|
end_sql
|
905
961
|
|
906
962
|
if result.nil? or result.empty?
|
907
|
-
|
908
|
-
# Support the 7.x and 8.0 nextval('foo'::text) as well as
|
909
|
-
# the 8.1+ nextval('foo'::regclass).
|
910
|
-
result = query(<<-end_sql, 'PK and custom sequence')[0]
|
963
|
+
result = query(<<-end_sql, 'SCHEMA')[0]
|
911
964
|
SELECT attr.attname,
|
912
965
|
CASE
|
913
|
-
WHEN split_part(def.
|
914
|
-
substr(split_part(def.
|
915
|
-
strpos(split_part(def.
|
916
|
-
ELSE split_part(def.
|
966
|
+
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
967
|
+
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
968
|
+
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
969
|
+
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
917
970
|
END
|
918
971
|
FROM pg_class t
|
919
972
|
JOIN pg_attribute attr ON (t.oid = attrelid)
|
@@ -921,7 +974,7 @@ module ActiveRecord
|
|
921
974
|
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
922
975
|
WHERE t.oid = '#{quote_table_name(table)}'::regclass
|
923
976
|
AND cons.contype = 'p'
|
924
|
-
AND def.
|
977
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
|
925
978
|
end_sql
|
926
979
|
end
|
927
980
|
|
@@ -932,25 +985,31 @@ module ActiveRecord
|
|
932
985
|
|
933
986
|
# Returns just a table's primary key
|
934
987
|
def primary_key(table)
|
935
|
-
row = exec_query(<<-end_sql, 'SCHEMA'
|
936
|
-
SELECT
|
988
|
+
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
|
989
|
+
SELECT attr.attname
|
937
990
|
FROM pg_attribute attr
|
938
|
-
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
|
939
991
|
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
|
940
992
|
WHERE cons.contype = 'p'
|
941
|
-
AND
|
993
|
+
AND cons.conrelid = '#{quote_table_name(table)}'::regclass
|
942
994
|
end_sql
|
943
995
|
|
944
996
|
row && row.first
|
945
997
|
end
|
946
998
|
|
947
999
|
# Renames a table.
|
1000
|
+
# Also renames a table's primary key sequence if the sequence name matches the
|
1001
|
+
# Active Record default.
|
948
1002
|
#
|
949
1003
|
# Example:
|
950
1004
|
# rename_table('octopuses', 'octopi')
|
951
1005
|
def rename_table(name, new_name)
|
952
1006
|
clear_cache!
|
953
1007
|
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
1008
|
+
pk, seq = pk_and_sequence_for(new_name)
|
1009
|
+
if seq == "#{name}_#{pk}_seq"
|
1010
|
+
new_seq = "#{new_name}_#{pk}_seq"
|
1011
|
+
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
1012
|
+
end
|
954
1013
|
end
|
955
1014
|
|
956
1015
|
# Adds a new column to the named table.
|
@@ -1008,14 +1067,32 @@ module ActiveRecord
|
|
1008
1067
|
|
1009
1068
|
# Maps logical Rails types to PostgreSQL-specific data types.
|
1010
1069
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
when
|
1017
|
-
|
1018
|
-
|
1070
|
+
case type.to_s
|
1071
|
+
when 'binary'
|
1072
|
+
# PostgreSQL doesn't support limits on binary (bytea) columns.
|
1073
|
+
# The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
|
1074
|
+
case limit
|
1075
|
+
when nil, 0..0x3fffffff; super(type)
|
1076
|
+
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
|
1077
|
+
end
|
1078
|
+
when 'text'
|
1079
|
+
# PostgreSQL doesn't support limits on text columns.
|
1080
|
+
# The hard limit is 1Gb, according to section 8.3 in the manual.
|
1081
|
+
case limit
|
1082
|
+
when nil, 0..0x3fffffff; super(type)
|
1083
|
+
else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
|
1084
|
+
end
|
1085
|
+
when 'integer'
|
1086
|
+
return 'integer' unless limit
|
1087
|
+
|
1088
|
+
case limit
|
1089
|
+
when 1, 2; 'smallint'
|
1090
|
+
when 3, 4; 'integer'
|
1091
|
+
when 5..8; 'bigint'
|
1092
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
1093
|
+
end
|
1094
|
+
else
|
1095
|
+
super
|
1019
1096
|
end
|
1020
1097
|
end
|
1021
1098
|
|
@@ -1030,24 +1107,49 @@ module ActiveRecord
|
|
1030
1107
|
|
1031
1108
|
# Construct a clean list of column names from the ORDER BY clause, removing
|
1032
1109
|
# any ASC/DESC modifiers
|
1033
|
-
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s
|
1110
|
+
order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
|
1034
1111
|
order_columns.delete_if { |c| c.blank? }
|
1035
1112
|
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
|
1036
1113
|
|
1037
1114
|
"DISTINCT #{columns}, #{order_columns * ', '}"
|
1038
1115
|
end
|
1039
1116
|
|
1117
|
+
module Utils
|
1118
|
+
extend self
|
1119
|
+
|
1120
|
+
# Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
|
1121
|
+
# +schema_name+ is nil if not specified in +name+.
|
1122
|
+
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
|
1123
|
+
# +name+ supports the range of schema/table references understood by PostgreSQL, for example:
|
1124
|
+
#
|
1125
|
+
# * <tt>table_name</tt>
|
1126
|
+
# * <tt>"table.name"</tt>
|
1127
|
+
# * <tt>schema_name.table_name</tt>
|
1128
|
+
# * <tt>schema_name."table.name"</tt>
|
1129
|
+
# * <tt>"schema.name"."table name"</tt>
|
1130
|
+
def extract_schema_and_table(name)
|
1131
|
+
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
|
1132
|
+
[schema, table]
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
|
1040
1136
|
protected
|
1041
1137
|
# Returns the version of the connected PostgreSQL server.
|
1042
1138
|
def postgresql_version
|
1043
1139
|
@connection.server_version
|
1044
1140
|
end
|
1045
1141
|
|
1142
|
+
# See http://www.postgresql.org/docs/9.1/static/errcodes-appendix.html
|
1143
|
+
FOREIGN_KEY_VIOLATION = "23503"
|
1144
|
+
UNIQUE_VIOLATION = "23505"
|
1145
|
+
|
1046
1146
|
def translate_exception(exception, message)
|
1047
|
-
|
1048
|
-
|
1147
|
+
return exception unless exception.respond_to?(:result)
|
1148
|
+
|
1149
|
+
case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE)
|
1150
|
+
when UNIQUE_VIOLATION
|
1049
1151
|
RecordNotUnique.new(message, exception)
|
1050
|
-
when
|
1152
|
+
when FOREIGN_KEY_VIOLATION
|
1051
1153
|
InvalidForeignKey.new(message, exception)
|
1052
1154
|
else
|
1053
1155
|
super
|
@@ -1058,7 +1160,7 @@ module ActiveRecord
|
|
1058
1160
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
1059
1161
|
|
1060
1162
|
def exec_no_cache(sql, binds)
|
1061
|
-
@connection.async_exec(sql)
|
1163
|
+
@connection.async_exec(sql, [])
|
1062
1164
|
end
|
1063
1165
|
|
1064
1166
|
def exec_cache(sql, binds)
|
@@ -1077,7 +1179,11 @@ module ActiveRecord
|
|
1077
1179
|
# prepared statements whose return value may have changed is
|
1078
1180
|
# FEATURE_NOT_SUPPORTED. Check here for more details:
|
1079
1181
|
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
|
1080
|
-
|
1182
|
+
begin
|
1183
|
+
code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
|
1184
|
+
rescue
|
1185
|
+
raise e
|
1186
|
+
end
|
1081
1187
|
if FEATURE_NOT_SUPPORTED == code
|
1082
1188
|
@statements.delete sql_key(sql)
|
1083
1189
|
retry
|
@@ -1146,7 +1252,7 @@ module ActiveRecord
|
|
1146
1252
|
|
1147
1253
|
# Returns the current ID of a table's sequence.
|
1148
1254
|
def last_insert_id(sequence_name) #:nodoc:
|
1149
|
-
r = exec_query("SELECT currval(
|
1255
|
+
r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
|
1150
1256
|
Integer(r.rows.first.first)
|
1151
1257
|
end
|
1152
1258
|
|
@@ -1184,7 +1290,8 @@ module ActiveRecord
|
|
1184
1290
|
# - ::regclass is a function that gives the id for a table name
|
1185
1291
|
def column_definitions(table_name) #:nodoc:
|
1186
1292
|
exec_query(<<-end_sql, 'SCHEMA').rows
|
1187
|
-
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1293
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
1294
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
|
1188
1295
|
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
1189
1296
|
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
1190
1297
|
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
@@ -1203,9 +1310,14 @@ module ActiveRecord
|
|
1203
1310
|
end
|
1204
1311
|
end
|
1205
1312
|
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1313
|
+
def extract_table_ref_from_insert_sql(sql)
|
1314
|
+
sql[/into\s+([^\(]*).*values\s*\(/i]
|
1315
|
+
$1.strip if $1
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
def table_definition
|
1319
|
+
TableDefinition.new(self)
|
1320
|
+
end
|
1209
1321
|
end
|
1210
1322
|
end
|
1211
1323
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class SchemaCache
|
4
|
+
attr_reader :primary_keys, :tables
|
5
|
+
attr_reader :connection
|
6
|
+
|
7
|
+
def initialize(conn)
|
8
|
+
@connection = conn
|
9
|
+
@tables = {}
|
10
|
+
|
11
|
+
@columns = Hash.new do |h, table_name|
|
12
|
+
h[table_name] = connection.columns(table_name, "#{table_name} Columns")
|
13
|
+
end
|
14
|
+
|
15
|
+
@columns_hash = Hash.new do |h, table_name|
|
16
|
+
h[table_name] = Hash[columns[table_name].map { |col|
|
17
|
+
[col.name, col]
|
18
|
+
}]
|
19
|
+
end
|
20
|
+
|
21
|
+
@primary_keys = Hash.new do |h, table_name|
|
22
|
+
h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A cached lookup for table existence.
|
27
|
+
def table_exists?(name)
|
28
|
+
return @tables[name] if @tables.key? name
|
29
|
+
|
30
|
+
@tables[name] = connection.table_exists?(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the columns for a table
|
34
|
+
def columns(table = nil)
|
35
|
+
if table
|
36
|
+
@columns[table]
|
37
|
+
else
|
38
|
+
@columns
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the columns for a table as a hash, key is the column name
|
43
|
+
# value is the column object.
|
44
|
+
def columns_hash(table = nil)
|
45
|
+
if table
|
46
|
+
@columns_hash[table]
|
47
|
+
else
|
48
|
+
@columns_hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Clears out internal caches
|
53
|
+
def clear!
|
54
|
+
@columns.clear
|
55
|
+
@columns_hash.clear
|
56
|
+
@primary_keys.clear
|
57
|
+
@tables.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
# Clear out internal caches for table with +table_name+.
|
61
|
+
def clear_table_cache!(table_name)
|
62
|
+
@columns.delete table_name
|
63
|
+
@columns_hash.delete table_name
|
64
|
+
@primary_keys.delete table_name
|
65
|
+
@tables.delete table_name
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'active_record/connection_adapters/sqlite_adapter'
|
2
2
|
|
3
|
-
gem 'sqlite3', '~> 1.3.
|
3
|
+
gem 'sqlite3', '~> 1.3.5'
|
4
4
|
require 'sqlite3'
|
5
5
|
|
6
6
|
module ActiveRecord
|
@@ -47,11 +47,7 @@ module ActiveRecord
|
|
47
47
|
|
48
48
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
49
49
|
def encoding
|
50
|
-
|
51
|
-
@connection.encoding.to_s
|
52
|
-
else
|
53
|
-
@connection.execute('PRAGMA encoding')[0]['encoding']
|
54
|
-
end
|
50
|
+
@connection.encoding.to_s
|
55
51
|
end
|
56
52
|
|
57
53
|
end
|