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.

Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +804 -338
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +13 -45
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  11. data/lib/active_record/associations/builder/association.rb +6 -4
  12. data/lib/active_record/associations/builder/belongs_to.rb +7 -4
  13. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +4 -4
  15. data/lib/active_record/associations/builder/has_one.rb +5 -6
  16. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  17. data/lib/active_record/associations/collection_association.rb +65 -32
  18. data/lib/active_record/associations/collection_proxy.rb +8 -41
  19. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  20. data/lib/active_record/associations/has_many_association.rb +11 -7
  21. data/lib/active_record/associations/has_many_through_association.rb +19 -9
  22. data/lib/active_record/associations/has_one_association.rb +23 -13
  23. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  24. data/lib/active_record/associations/join_dependency.rb +3 -3
  25. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  26. data/lib/active_record/associations/preloader.rb +14 -10
  27. data/lib/active_record/associations/through_association.rb +8 -4
  28. data/lib/active_record/associations.rb +92 -76
  29. data/lib/active_record/attribute_assignment.rb +221 -0
  30. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  32. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  33. data/lib/active_record/attribute_methods/read.rb +73 -83
  34. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  36. data/lib/active_record/attribute_methods/write.rb +32 -6
  37. data/lib/active_record/attribute_methods.rb +231 -30
  38. data/lib/active_record/autosave_association.rb +44 -26
  39. data/lib/active_record/base.rb +227 -1708
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  41. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  49. data/lib/active_record/connection_adapters/column.rb +37 -11
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
  51. data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
  53. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  55. data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
  56. data/lib/active_record/counter_cache.rb +9 -4
  57. data/lib/active_record/dynamic_finder_match.rb +12 -0
  58. data/lib/active_record/dynamic_matchers.rb +84 -0
  59. data/lib/active_record/errors.rb +11 -1
  60. data/lib/active_record/explain.rb +86 -0
  61. data/lib/active_record/explain_subscriber.rb +25 -0
  62. data/lib/active_record/fixtures/file.rb +65 -0
  63. data/lib/active_record/fixtures.rb +57 -86
  64. data/lib/active_record/identity_map.rb +3 -4
  65. data/lib/active_record/inheritance.rb +174 -0
  66. data/lib/active_record/integration.rb +60 -0
  67. data/lib/active_record/locking/optimistic.rb +33 -26
  68. data/lib/active_record/locking/pessimistic.rb +23 -1
  69. data/lib/active_record/log_subscriber.rb +8 -4
  70. data/lib/active_record/migration/command_recorder.rb +8 -8
  71. data/lib/active_record/migration.rb +68 -35
  72. data/lib/active_record/model_schema.rb +368 -0
  73. data/lib/active_record/nested_attributes.rb +60 -24
  74. data/lib/active_record/persistence.rb +57 -11
  75. data/lib/active_record/query_cache.rb +6 -6
  76. data/lib/active_record/querying.rb +58 -0
  77. data/lib/active_record/railtie.rb +37 -29
  78. data/lib/active_record/railties/controller_runtime.rb +3 -1
  79. data/lib/active_record/railties/databases.rake +213 -117
  80. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  81. data/lib/active_record/readonly_attributes.rb +26 -0
  82. data/lib/active_record/reflection.rb +7 -15
  83. data/lib/active_record/relation/batches.rb +7 -4
  84. data/lib/active_record/relation/calculations.rb +55 -16
  85. data/lib/active_record/relation/delegation.rb +49 -0
  86. data/lib/active_record/relation/finder_methods.rb +16 -11
  87. data/lib/active_record/relation/predicate_builder.rb +8 -6
  88. data/lib/active_record/relation/query_methods.rb +75 -9
  89. data/lib/active_record/relation/spawn_methods.rb +48 -7
  90. data/lib/active_record/relation.rb +78 -32
  91. data/lib/active_record/result.rb +10 -4
  92. data/lib/active_record/sanitization.rb +194 -0
  93. data/lib/active_record/schema_dumper.rb +12 -5
  94. data/lib/active_record/scoping/default.rb +142 -0
  95. data/lib/active_record/scoping/named.rb +200 -0
  96. data/lib/active_record/scoping.rb +152 -0
  97. data/lib/active_record/serialization.rb +1 -43
  98. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  99. data/lib/active_record/session_store.rb +18 -16
  100. data/lib/active_record/store.rb +52 -0
  101. data/lib/active_record/test_case.rb +11 -7
  102. data/lib/active_record/timestamp.rb +17 -3
  103. data/lib/active_record/transactions.rb +27 -6
  104. data/lib/active_record/translation.rb +22 -0
  105. data/lib/active_record/validations/associated.rb +5 -4
  106. data/lib/active_record/validations/uniqueness.rb +8 -8
  107. data/lib/active_record/validations.rb +1 -1
  108. data/lib/active_record/version.rb +3 -3
  109. data/lib/active_record.rb +38 -3
  110. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  111. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  112. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  113. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  114. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  115. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  116. metadata +49 -28
  117. 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'(.*)'::(?:character varying|bpchar|text)\z/m
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. This is backward-compatible with the <tt>:schema_order</tt> option.
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
- connection_parameters.delete :prepared_statements
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.status == PGconn::CONNECTION_OK
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 /^[01]*$/ then "B'#{value}'" # Bit-string notation
444
- when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
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
- # Extract the table from the insert sql. Yuck.
539
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
540
-
541
- pk ||= primary_key(table)
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
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
638
-
639
- pk = primary_key(table)
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
- def recreate_database(name) #:nodoc:
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. Options include <tt>:owner</tt>, <tt>:template</tt>,
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 # Abstract classes is having nil table name
788
+ schema, table = Utils.extract_schema_and_table(name.to_s)
789
+ return false unless table
740
790
 
741
- binds = [[nil, table.gsub(/(^"|"$)/,'')]]
791
+ binds = [[nil, table]]
742
792
  binds << [nil, schema] if schema
743
793
 
744
- exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
794
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
745
795
  SELECT COUNT(*)
746
- FROM pg_tables
747
- WHERE tablename = $1
748
- AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"}
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
- # Extracts the table and schema name from +name+
753
- def extract_schema_and_table(name)
754
- schema, table = name.split('.', 2)
755
-
756
- unless table # A table was provided without a schema
757
- table = schema
758
- schema = nil
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, name)
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
- oid = row[3]
832
+ inddef = row[3]
833
+ oid = row[4]
788
834
 
789
- columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
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
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
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 "SET search_path TO #{schema_csv}"
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', [[nil, table], [nil, column]])
858
- SELECT pg_get_serial_sequence($1, $2)
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 = quote_column_name(sequence)
933
+ quoted_sequence = quote_table_name(sequence)
878
934
 
879
- select_value <<-end_sql, 'Reset sequence'
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, 'PK and serial sequence')[0]
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
- # If that fails, try parsing the primary key's default value.
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.adsrc, '''', 2) ~ '.' THEN
914
- substr(split_part(def.adsrc, '''', 2),
915
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
916
- ELSE split_part(def.adsrc, '''', 2)
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.adsrc ~* 'nextval'
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', [[nil, table]]).rows.first
936
- SELECT DISTINCT(attr.attname)
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 dep.refobjid = $1::regclass
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
- return super unless type.to_s == 'integer'
1012
- return 'integer' unless limit
1013
-
1014
- case limit
1015
- when 1, 2; 'smallint'
1016
- when 3, 4; 'integer'
1017
- when 5..8; 'bigint'
1018
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
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*/i, '') }
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
- case exception.message
1048
- when /duplicate key value violates unique constraint/
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 /violates foreign key constraint/
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
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
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($1)", 'SQL', [[nil, sequence_name]])
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), d.adsrc, a.attnotnull
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
- def table_definition
1207
- TableDefinition.new(self)
1208
- end
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.4'
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
- if @connection.respond_to?(:encoding)
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