activerecord 3.1.9 → 3.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +317 -336
  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 +3 -42
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +64 -31
  16. data/lib/active_record/associations/collection_proxy.rb +2 -37
  17. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  18. data/lib/active_record/associations/has_many_association.rb +5 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +28 -9
  20. data/lib/active_record/associations/has_one_association.rb +15 -13
  21. data/lib/active_record/associations/join_dependency.rb +2 -2
  22. data/lib/active_record/associations/preloader.rb +14 -10
  23. data/lib/active_record/associations/through_association.rb +7 -3
  24. data/lib/active_record/associations.rb +92 -76
  25. data/lib/active_record/attribute_assignment.rb +221 -0
  26. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  27. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  28. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  29. data/lib/active_record/attribute_methods/read.rb +73 -83
  30. data/lib/active_record/attribute_methods/serialization.rb +102 -0
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
  32. data/lib/active_record/attribute_methods/write.rb +31 -6
  33. data/lib/active_record/attribute_methods.rb +231 -30
  34. data/lib/active_record/autosave_association.rb +43 -22
  35. data/lib/active_record/base.rb +227 -1708
  36. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  37. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  38. data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
  39. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  40. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
  45. data/lib/active_record/connection_adapters/column.rb +37 -11
  46. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
  47. data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
  49. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  50. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  51. data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
  52. data/lib/active_record/counter_cache.rb +9 -4
  53. data/lib/active_record/dynamic_finder_match.rb +12 -0
  54. data/lib/active_record/dynamic_matchers.rb +84 -0
  55. data/lib/active_record/errors.rb +11 -1
  56. data/lib/active_record/explain.rb +85 -0
  57. data/lib/active_record/explain_subscriber.rb +25 -0
  58. data/lib/active_record/fixtures/file.rb +65 -0
  59. data/lib/active_record/fixtures.rb +56 -85
  60. data/lib/active_record/identity_map.rb +3 -4
  61. data/lib/active_record/inheritance.rb +174 -0
  62. data/lib/active_record/integration.rb +49 -0
  63. data/lib/active_record/locking/optimistic.rb +30 -25
  64. data/lib/active_record/locking/pessimistic.rb +23 -1
  65. data/lib/active_record/log_subscriber.rb +3 -3
  66. data/lib/active_record/migration/command_recorder.rb +8 -8
  67. data/lib/active_record/migration.rb +68 -35
  68. data/lib/active_record/model_schema.rb +366 -0
  69. data/lib/active_record/nested_attributes.rb +3 -2
  70. data/lib/active_record/persistence.rb +57 -11
  71. data/lib/active_record/querying.rb +58 -0
  72. data/lib/active_record/railtie.rb +31 -29
  73. data/lib/active_record/railties/controller_runtime.rb +3 -1
  74. data/lib/active_record/railties/databases.rake +191 -110
  75. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  76. data/lib/active_record/readonly_attributes.rb +26 -0
  77. data/lib/active_record/reflection.rb +7 -15
  78. data/lib/active_record/relation/batches.rb +5 -2
  79. data/lib/active_record/relation/calculations.rb +47 -15
  80. data/lib/active_record/relation/delegation.rb +49 -0
  81. data/lib/active_record/relation/finder_methods.rb +9 -7
  82. data/lib/active_record/relation/predicate_builder.rb +18 -7
  83. data/lib/active_record/relation/query_methods.rb +75 -9
  84. data/lib/active_record/relation/spawn_methods.rb +11 -2
  85. data/lib/active_record/relation.rb +78 -32
  86. data/lib/active_record/result.rb +1 -1
  87. data/lib/active_record/sanitization.rb +194 -0
  88. data/lib/active_record/schema_dumper.rb +12 -5
  89. data/lib/active_record/scoping/default.rb +142 -0
  90. data/lib/active_record/scoping/named.rb +202 -0
  91. data/lib/active_record/scoping.rb +152 -0
  92. data/lib/active_record/serialization.rb +1 -43
  93. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  94. data/lib/active_record/session_store.rb +17 -15
  95. data/lib/active_record/store.rb +52 -0
  96. data/lib/active_record/test_case.rb +11 -7
  97. data/lib/active_record/timestamp.rb +17 -3
  98. data/lib/active_record/transactions.rb +27 -6
  99. data/lib/active_record/translation.rb +22 -0
  100. data/lib/active_record/validations/associated.rb +5 -4
  101. data/lib/active_record/validations/uniqueness.rb +7 -7
  102. data/lib/active_record/validations.rb +1 -1
  103. data/lib/active_record/version.rb +2 -2
  104. data/lib/active_record.rb +38 -3
  105. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  106. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  107. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  108. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  109. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  110. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  111. metadata +30 -10
  112. 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'
@@ -146,11 +145,8 @@ module ActiveRecord
146
145
  when /\A\(?(-?\d+(\.\d*)?\)?)\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,6 +309,12 @@ module ActiveRecord
309
309
  def initialize(connection, logger, connection_parameters, config)
310
310
  super(connection, logger)
311
311
 
312
+ if config.fetch(:prepared_statements) { true }
313
+ @visitor = Arel::Visitors::PostgreSQL.new self
314
+ else
315
+ @visitor = BindSubstitution.new self
316
+ end
317
+
312
318
  connection_parameters.delete :prepared_statements
313
319
 
314
320
  @connection_parameters, @config = connection_parameters, config
@@ -328,15 +334,6 @@ module ActiveRecord
328
334
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
329
335
  end
330
336
 
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
337
  # Clears the prepared statements cache.
341
338
  def clear_cache!
342
339
  @statements.clear
@@ -344,7 +341,8 @@ module ActiveRecord
344
341
 
345
342
  # Is this connection alive and ready for queries?
346
343
  def active?
347
- @connection.status == PGconn::CONNECTION_OK
344
+ @connection.query 'SELECT 1'
345
+ true
348
346
  rescue PGError
349
347
  false
350
348
  end
@@ -353,6 +351,7 @@ module ActiveRecord
353
351
  def reconnect!
354
352
  clear_cache!
355
353
  @connection.reset
354
+ @open_transactions = 0
356
355
  configure_connection
357
356
  end
358
357
 
@@ -403,6 +402,11 @@ module ActiveRecord
403
402
  true
404
403
  end
405
404
 
405
+ # Returns true.
406
+ def supports_explain?
407
+ true
408
+ end
409
+
406
410
  # Returns the configured supported identifier length supported by PostgreSQL
407
411
  def table_alias_length
408
412
  @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
@@ -527,6 +531,48 @@ module ActiveRecord
527
531
 
528
532
  # DATABASE STATEMENTS ======================================
529
533
 
534
+ def explain(arel, binds = [])
535
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
536
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
537
+ end
538
+
539
+ class ExplainPrettyPrinter # :nodoc:
540
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
541
+ # PostgreSQL shell:
542
+ #
543
+ # QUERY PLAN
544
+ # ------------------------------------------------------------------------------
545
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
546
+ # Join Filter: (posts.user_id = users.id)
547
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
548
+ # Index Cond: (id = 1)
549
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
550
+ # Filter: (posts.user_id = 1)
551
+ # (6 rows)
552
+ #
553
+ def pp(result)
554
+ header = result.columns.first
555
+ lines = result.rows.map(&:first)
556
+
557
+ # We add 2 because there's one char of padding at both sides, note
558
+ # the extra hyphens in the example above.
559
+ width = [header, *lines].map(&:length).max + 2
560
+
561
+ pp = []
562
+
563
+ pp << header.center(width).rstrip
564
+ pp << '-' * width
565
+
566
+ pp += lines.map {|line| " #{line}"}
567
+
568
+ nrows = result.rows.length
569
+ rows_label = nrows == 1 ? 'row' : 'rows'
570
+ pp << "(#{nrows} #{rows_label})"
571
+
572
+ pp.join("\n") + "\n"
573
+ end
574
+ end
575
+
530
576
  # Executes a SELECT query and returns an array of rows. Each row is an
531
577
  # array of field values.
532
578
  def select_rows(sql, name = nil)
@@ -535,10 +581,11 @@ module ActiveRecord
535
581
 
536
582
  # Executes an INSERT query and returns the new record's ID
537
583
  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)
584
+ unless pk
585
+ # Extract the table from the insert sql. Yuck.
586
+ table_ref = extract_table_ref_from_insert_sql(sql)
587
+ pk = primary_key(table_ref) if table_ref
588
+ end
542
589
 
543
590
  if pk
544
591
  select_value("#{sql} RETURNING #{quote_column_name(pk)}")
@@ -634,9 +681,9 @@ module ActiveRecord
634
681
 
635
682
  def sql_for_insert(sql, pk, id_value, sequence_name, binds)
636
683
  unless pk
637
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
638
-
639
- pk = primary_key(table)
684
+ # Extract the table from the insert sql. Yuck.
685
+ table_ref = extract_table_ref_from_insert_sql(sql)
686
+ pk = primary_key(table_ref) if table_ref
640
687
  end
641
688
 
642
689
  sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
@@ -682,12 +729,14 @@ module ActiveRecord
682
729
 
683
730
  # SCHEMA STATEMENTS ========================================
684
731
 
685
- def recreate_database(name) #:nodoc:
732
+ # Drops the database specified on the +name+ attribute
733
+ # and creates it again using the provided +options+.
734
+ def recreate_database(name, options = {}) #:nodoc:
686
735
  drop_database(name)
687
- create_database(name)
736
+ create_database(name, options)
688
737
  end
689
738
 
690
- # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
739
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
691
740
  # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
692
741
  # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
693
742
  #
@@ -734,41 +783,39 @@ module ActiveRecord
734
783
  SQL
735
784
  end
736
785
 
786
+ # Returns true if table exists.
787
+ # If the schema is not specified as part of +name+ then it will only find tables within
788
+ # the current schema search path (regardless of permissions to access tables in other schemas)
737
789
  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
790
+ schema, table = Utils.extract_schema_and_table(name.to_s)
791
+ return false unless table
740
792
 
741
- binds = [[nil, table.gsub(/(^"|"$)/,'')]]
793
+ binds = [[nil, table]]
742
794
  binds << [nil, schema] if schema
743
795
 
744
- exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
796
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
745
797
  SELECT COUNT(*)
746
- FROM pg_tables
747
- WHERE tablename = $1
748
- AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"}
798
+ FROM pg_class c
799
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
800
+ WHERE c.relkind in ('v','r')
801
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
802
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
749
803
  SQL
750
804
  end
751
805
 
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]
806
+ # Returns true if schema exists.
807
+ def schema_exists?(name)
808
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
809
+ SELECT COUNT(*)
810
+ FROM pg_namespace
811
+ WHERE nspname = '#{name}'
812
+ SQL
766
813
  end
767
814
 
768
815
  # Returns an array of indexes for the given table.
769
816
  def indexes(table_name, name = nil)
770
- result = query(<<-SQL, name)
771
- SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
817
+ result = query(<<-SQL, 'SCHEMA')
818
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
772
819
  FROM pg_class t
773
820
  INNER JOIN pg_index d ON t.oid = d.indrelid
774
821
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -784,9 +831,10 @@ module ActiveRecord
784
831
  index_name = row[0]
785
832
  unique = row[1] == 't'
786
833
  indkey = row[2].split(" ")
787
- oid = row[3]
834
+ inddef = row[3]
835
+ oid = row[4]
788
836
 
789
- columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
837
+ columns = Hash[query(<<-SQL, "SCHEMA")]
790
838
  SELECT a.attnum, a.attname
791
839
  FROM pg_attribute a
792
840
  WHERE a.attrelid = #{oid}
@@ -794,7 +842,12 @@ module ActiveRecord
794
842
  SQL
795
843
 
796
844
  column_names = columns.values_at(*indkey).compact
797
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
845
+
846
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
847
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
848
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
849
+
850
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
798
851
  end.compact
799
852
  end
800
853
 
@@ -808,12 +861,17 @@ module ActiveRecord
808
861
 
809
862
  # Returns the current database name.
810
863
  def current_database
811
- query('select current_database()')[0][0]
864
+ query('select current_database()', 'SCHEMA')[0][0]
865
+ end
866
+
867
+ # Returns the current schema name.
868
+ def current_schema
869
+ query('SELECT current_schema', 'SCHEMA')[0][0]
812
870
  end
813
871
 
814
872
  # Returns the current database encoding format.
815
873
  def encoding
816
- query(<<-end_sql)[0][0]
874
+ query(<<-end_sql, 'SCHEMA')[0][0]
817
875
  SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
818
876
  WHERE pg_database.datname LIKE '#{current_database}'
819
877
  end_sql
@@ -826,14 +884,14 @@ module ActiveRecord
826
884
  # This should be not be called manually but set in database.yml.
827
885
  def schema_search_path=(schema_csv)
828
886
  if schema_csv
829
- execute "SET search_path TO #{schema_csv}"
887
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
830
888
  @schema_search_path = schema_csv
831
889
  end
832
890
  end
833
891
 
834
892
  # Returns the active schema search path.
835
893
  def schema_search_path
836
- @schema_search_path ||= query('SHOW search_path')[0][0]
894
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
837
895
  end
838
896
 
839
897
  # Returns the current client message level.
@@ -854,8 +912,8 @@ module ActiveRecord
854
912
  end
855
913
 
856
914
  def serial_sequence(table, column)
857
- result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
858
- SELECT pg_get_serial_sequence($1, $2)
915
+ result = exec_query(<<-eosql, 'SCHEMA')
916
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
859
917
  eosql
860
918
  result.rows.first.first
861
919
  end
@@ -874,9 +932,9 @@ module ActiveRecord
874
932
  end
875
933
 
876
934
  if pk && sequence
877
- quoted_sequence = quote_column_name(sequence)
935
+ quoted_sequence = quote_table_name(sequence)
878
936
 
879
- select_value <<-end_sql, 'Reset sequence'
937
+ select_value <<-end_sql, 'SCHEMA'
880
938
  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
939
  end_sql
882
940
  end
@@ -886,7 +944,7 @@ module ActiveRecord
886
944
  def pk_and_sequence_for(table) #:nodoc:
887
945
  # First try looking for a sequence with a dependency on the
888
946
  # given table's primary key.
889
- result = query(<<-end_sql, 'PK and serial sequence')[0]
947
+ result = query(<<-end_sql, 'SCHEMA')[0]
890
948
  SELECT attr.attname, seq.relname
891
949
  FROM pg_class seq,
892
950
  pg_attribute attr,
@@ -904,16 +962,13 @@ module ActiveRecord
904
962
  end_sql
905
963
 
906
964
  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]
965
+ result = query(<<-end_sql, 'SCHEMA')[0]
911
966
  SELECT attr.attname,
912
967
  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)
968
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
969
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
970
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
971
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
917
972
  END
918
973
  FROM pg_class t
919
974
  JOIN pg_attribute attr ON (t.oid = attrelid)
@@ -921,7 +976,7 @@ module ActiveRecord
921
976
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
922
977
  WHERE t.oid = '#{quote_table_name(table)}'::regclass
923
978
  AND cons.contype = 'p'
924
- AND def.adsrc ~* 'nextval'
979
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
925
980
  end_sql
926
981
  end
927
982
 
@@ -932,25 +987,32 @@ module ActiveRecord
932
987
 
933
988
  # Returns just a table's primary key
934
989
  def primary_key(table)
935
- row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
990
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
936
991
  SELECT DISTINCT(attr.attname)
937
992
  FROM pg_attribute attr
938
993
  INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
939
994
  INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
940
995
  WHERE cons.contype = 'p'
941
- AND dep.refobjid = $1::regclass
996
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
942
997
  end_sql
943
998
 
944
999
  row && row.first
945
1000
  end
946
1001
 
947
1002
  # Renames a table.
1003
+ # Also renames a table's primary key sequence if the sequence name matches the
1004
+ # Active Record default.
948
1005
  #
949
1006
  # Example:
950
1007
  # rename_table('octopuses', 'octopi')
951
1008
  def rename_table(name, new_name)
952
1009
  clear_cache!
953
1010
  execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
1011
+ pk, seq = pk_and_sequence_for(new_name)
1012
+ if seq == "#{name}_#{pk}_seq"
1013
+ new_seq = "#{new_name}_#{pk}_seq"
1014
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
1015
+ end
954
1016
  end
955
1017
 
956
1018
  # Adds a new column to the named table.
@@ -1008,14 +1070,25 @@ module ActiveRecord
1008
1070
 
1009
1071
  # Maps logical Rails types to PostgreSQL-specific data types.
1010
1072
  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.")
1073
+ case type.to_s
1074
+ when 'binary'
1075
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
1076
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
1077
+ case limit
1078
+ when nil, 0..0x3fffffff; super(type)
1079
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
1080
+ end
1081
+ when 'integer'
1082
+ return 'integer' unless limit
1083
+
1084
+ case limit
1085
+ when 1, 2; 'smallint'
1086
+ when 3, 4; 'integer'
1087
+ when 5..8; 'bigint'
1088
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
1089
+ end
1090
+ else
1091
+ super
1019
1092
  end
1020
1093
  end
1021
1094
 
@@ -1030,13 +1103,32 @@ module ActiveRecord
1030
1103
 
1031
1104
  # Construct a clean list of column names from the ORDER BY clause, removing
1032
1105
  # any ASC/DESC modifiers
1033
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
1106
+ order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '') }
1034
1107
  order_columns.delete_if { |c| c.blank? }
1035
1108
  order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
1036
1109
 
1037
1110
  "DISTINCT #{columns}, #{order_columns * ', '}"
1038
1111
  end
1039
1112
 
1113
+ module Utils
1114
+ extend self
1115
+
1116
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1117
+ # +schema_name+ is nil if not specified in +name+.
1118
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1119
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
1120
+ #
1121
+ # * <tt>table_name</tt>
1122
+ # * <tt>"table.name"</tt>
1123
+ # * <tt>schema_name.table_name</tt>
1124
+ # * <tt>schema_name."table.name"</tt>
1125
+ # * <tt>"schema.name"."table name"</tt>
1126
+ def extract_schema_and_table(name)
1127
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1128
+ [schema, table]
1129
+ end
1130
+ end
1131
+
1040
1132
  protected
1041
1133
  # Returns the version of the connected PostgreSQL server.
1042
1134
  def postgresql_version
@@ -1146,7 +1238,7 @@ module ActiveRecord
1146
1238
 
1147
1239
  # Returns the current ID of a table's sequence.
1148
1240
  def last_insert_id(sequence_name) #:nodoc:
1149
- r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
1241
+ r = exec_query("SELECT currval('#{sequence_name}')", 'SQL')
1150
1242
  Integer(r.rows.first.first)
1151
1243
  end
1152
1244
 
@@ -1184,7 +1276,8 @@ module ActiveRecord
1184
1276
  # - ::regclass is a function that gives the id for a table name
1185
1277
  def column_definitions(table_name) #:nodoc:
1186
1278
  exec_query(<<-end_sql, 'SCHEMA').rows
1187
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1279
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1280
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
1188
1281
  FROM pg_attribute a LEFT JOIN pg_attrdef d
1189
1282
  ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1190
1283
  WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -1203,9 +1296,14 @@ module ActiveRecord
1203
1296
  end
1204
1297
  end
1205
1298
 
1206
- def table_definition
1207
- TableDefinition.new(self)
1208
- end
1299
+ def extract_table_ref_from_insert_sql(sql)
1300
+ sql[/into\s+([^\(]*).*values\s*\(/i]
1301
+ $1.strip if $1
1302
+ end
1303
+
1304
+ def table_definition
1305
+ TableDefinition.new(self)
1306
+ end
1209
1307
  end
1210
1308
  end
1211
1309
  end
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class SchemaCache
4
+ attr_reader :columns, :columns_hash, :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] = conn.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) ? conn.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
+ # Clears out internal caches
34
+ def clear!
35
+ @columns.clear
36
+ @columns_hash.clear
37
+ @primary_keys.clear
38
+ @tables.clear
39
+ end
40
+
41
+ # Clear out internal caches for table with +table_name+.
42
+ def clear_table_cache!(table_name)
43
+ @columns.delete table_name
44
+ @columns_hash.delete table_name
45
+ @primary_keys.delete table_name
46
+ @tables.delete table_name
47
+ end
48
+ end
49
+ end
50
+ 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