activerecord 3.1.11 → 3.2.0

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 (106) hide show
  1. data/CHANGELOG.md +6294 -97
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/associations/association.rb +2 -42
  6. data/lib/active_record/associations/association_scope.rb +3 -30
  7. data/lib/active_record/associations/builder/association.rb +6 -4
  8. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/builder/has_many.rb +4 -4
  11. data/lib/active_record/associations/builder/has_one.rb +5 -6
  12. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  13. data/lib/active_record/associations/collection_association.rb +55 -28
  14. data/lib/active_record/associations/collection_proxy.rb +1 -35
  15. data/lib/active_record/associations/has_many_association.rb +5 -1
  16. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  17. data/lib/active_record/associations/join_dependency.rb +1 -1
  18. data/lib/active_record/associations/preloader/association.rb +3 -1
  19. data/lib/active_record/associations.rb +82 -69
  20. data/lib/active_record/attribute_assignment.rb +221 -0
  21. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  23. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  24. data/lib/active_record/attribute_methods/read.rb +72 -83
  25. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  27. data/lib/active_record/attribute_methods/write.rb +27 -5
  28. data/lib/active_record/attribute_methods.rb +209 -30
  29. data/lib/active_record/autosave_association.rb +23 -8
  30. data/lib/active_record/base.rb +217 -1709
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  33. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  34. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
  36. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  37. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  40. data/lib/active_record/connection_adapters/column.rb +2 -2
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +4 -3
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures/file.rb +65 -0
  53. data/lib/active_record/fixtures.rb +31 -76
  54. data/lib/active_record/identity_map.rb +4 -11
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +30 -25
  58. data/lib/active_record/locking/pessimistic.rb +23 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration/command_recorder.rb +8 -8
  61. data/lib/active_record/migration.rb +47 -30
  62. data/lib/active_record/model_schema.rb +366 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -9
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +134 -77
  69. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  70. data/lib/active_record/readonly_attributes.rb +26 -0
  71. data/lib/active_record/reflection.rb +7 -15
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +6 -5
  76. data/lib/active_record/relation/predicate_builder.rb +12 -19
  77. data/lib/active_record/relation/query_methods.rb +76 -10
  78. data/lib/active_record/relation/spawn_methods.rb +11 -2
  79. data/lib/active_record/relation.rb +77 -34
  80. data/lib/active_record/result.rb +1 -1
  81. data/lib/active_record/sanitization.rb +194 -0
  82. data/lib/active_record/schema_dumper.rb +5 -2
  83. data/lib/active_record/scoping/default.rb +142 -0
  84. data/lib/active_record/scoping/named.rb +202 -0
  85. data/lib/active_record/scoping.rb +152 -0
  86. data/lib/active_record/serialization.rb +1 -43
  87. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  88. data/lib/active_record/session_store.rb +15 -15
  89. data/lib/active_record/store.rb +50 -0
  90. data/lib/active_record/test_case.rb +11 -7
  91. data/lib/active_record/timestamp.rb +16 -3
  92. data/lib/active_record/transactions.rb +5 -5
  93. data/lib/active_record/translation.rb +22 -0
  94. data/lib/active_record/validations/associated.rb +5 -4
  95. data/lib/active_record/validations/uniqueness.rb +4 -4
  96. data/lib/active_record/validations.rb +1 -1
  97. data/lib/active_record/version.rb +2 -2
  98. data/lib/active_record.rb +28 -2
  99. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  100. data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
  101. data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
  102. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  103. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  104. metadata +50 -40
  105. checksums.yaml +0 -7
  106. data/lib/active_record/named_scope.rb +0 -200
@@ -1,8 +1,6 @@
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
- require 'arel/visitors/bind_visitor'
6
4
 
7
5
  # Make sure we're using pg high enough for PGResult#values
8
6
  gem 'pg', '~> 0.11'
@@ -201,7 +199,7 @@ module ActiveRecord
201
199
  # * <tt>:password</tt> - Defaults to nothing.
202
200
  # * <tt>:database</tt> - The name of the database. No default, must be provided.
203
201
  # * <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.
202
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
205
203
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
206
204
  # <encoding></tt> call on the connection.
207
205
  # * <tt>:min_messages</tt> - An optional client min messages that is used in a
@@ -249,6 +247,10 @@ module ActiveRecord
249
247
  true
250
248
  end
251
249
 
250
+ def supports_index_sort_order?
251
+ true
252
+ end
253
+
252
254
  class StatementPool < ConnectionAdapters::StatementPool
253
255
  def initialize(connection, max)
254
256
  super
@@ -301,17 +303,11 @@ module ActiveRecord
301
303
  end
302
304
  end
303
305
 
304
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
305
- include Arel::Visitors::BindVisitor
306
- end
307
-
308
306
  # Initializes and connects a PostgreSQL adapter.
309
307
  def initialize(connection, logger, connection_parameters, config)
310
308
  super(connection, logger)
311
-
312
- connection_parameters.delete :prepared_statements
313
-
314
309
  @connection_parameters, @config = connection_parameters, config
310
+ @visitor = Arel::Visitors::PostgreSQL.new self
315
311
 
316
312
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
317
313
  @local_tz = nil
@@ -328,15 +324,6 @@ module ActiveRecord
328
324
  @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
329
325
  end
330
326
 
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
327
  # Clears the prepared statements cache.
341
328
  def clear_cache!
342
329
  @statements.clear
@@ -403,6 +390,11 @@ module ActiveRecord
403
390
  true
404
391
  end
405
392
 
393
+ # Returns true.
394
+ def supports_explain?
395
+ true
396
+ end
397
+
406
398
  # Returns the configured supported identifier length supported by PostgreSQL
407
399
  def table_alias_length
408
400
  @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
@@ -527,6 +519,48 @@ module ActiveRecord
527
519
 
528
520
  # DATABASE STATEMENTS ======================================
529
521
 
522
+ def explain(arel, binds = [])
523
+ sql = "EXPLAIN #{to_sql(arel)}"
524
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
525
+ end
526
+
527
+ class ExplainPrettyPrinter # :nodoc:
528
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
529
+ # PostgreSQL shell:
530
+ #
531
+ # QUERY PLAN
532
+ # ------------------------------------------------------------------------------
533
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
534
+ # Join Filter: (posts.user_id = users.id)
535
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
536
+ # Index Cond: (id = 1)
537
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
538
+ # Filter: (posts.user_id = 1)
539
+ # (6 rows)
540
+ #
541
+ def pp(result)
542
+ header = result.columns.first
543
+ lines = result.rows.map(&:first)
544
+
545
+ # We add 2 because there's one char of padding at both sides, note
546
+ # the extra hyphens in the example above.
547
+ width = [header, *lines].map(&:length).max + 2
548
+
549
+ pp = []
550
+
551
+ pp << header.center(width).rstrip
552
+ pp << '-' * width
553
+
554
+ pp += lines.map {|line| " #{line}"}
555
+
556
+ nrows = result.rows.length
557
+ rows_label = nrows == 1 ? 'row' : 'rows'
558
+ pp << "(#{nrows} #{rows_label})"
559
+
560
+ pp.join("\n") + "\n"
561
+ end
562
+ end
563
+
530
564
  # Executes a SELECT query and returns an array of rows. Each row is an
531
565
  # array of field values.
532
566
  def select_rows(sql, name = nil)
@@ -535,10 +569,11 @@ module ActiveRecord
535
569
 
536
570
  # Executes an INSERT query and returns the new record's ID
537
571
  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)
572
+ unless pk
573
+ # Extract the table from the insert sql. Yuck.
574
+ table_ref = extract_table_ref_from_insert_sql(sql)
575
+ pk = primary_key(table_ref) if table_ref
576
+ end
542
577
 
543
578
  if pk
544
579
  select_value("#{sql} RETURNING #{quote_column_name(pk)}")
@@ -607,7 +642,7 @@ module ActiveRecord
607
642
  end
608
643
 
609
644
  def substitute_at(column, index)
610
- Arel::Nodes::BindParam.new "$#{index + 1}"
645
+ Arel.sql("$#{index + 1}")
611
646
  end
612
647
 
613
648
  def exec_query(sql, name = 'SQL', binds = [])
@@ -634,9 +669,9 @@ module ActiveRecord
634
669
 
635
670
  def sql_for_insert(sql, pk, id_value, sequence_name, binds)
636
671
  unless pk
637
- _, table = extract_schema_and_table(sql.split(" ", 4)[2])
638
-
639
- pk = primary_key(table)
672
+ # Extract the table from the insert sql. Yuck.
673
+ table_ref = extract_table_ref_from_insert_sql(sql)
674
+ pk = primary_key(table_ref) if table_ref
640
675
  end
641
676
 
642
677
  sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
@@ -682,12 +717,14 @@ module ActiveRecord
682
717
 
683
718
  # SCHEMA STATEMENTS ========================================
684
719
 
685
- def recreate_database(name) #:nodoc:
720
+ # Drops the database specified on the +name+ attribute
721
+ # and creates it again using the provided +options+.
722
+ def recreate_database(name, options = {}) #:nodoc:
686
723
  drop_database(name)
687
- create_database(name)
724
+ create_database(name, options)
688
725
  end
689
726
 
690
- # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
727
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
691
728
  # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
692
729
  # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
693
730
  #
@@ -734,41 +771,39 @@ module ActiveRecord
734
771
  SQL
735
772
  end
736
773
 
774
+ # Returns true if table exists.
775
+ # If the schema is not specified as part of +name+ then it will only find tables within
776
+ # the current schema search path (regardless of permissions to access tables in other schemas)
737
777
  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
778
+ schema, table = Utils.extract_schema_and_table(name.to_s)
779
+ return false unless table
740
780
 
741
- binds = [[nil, table.gsub(/(^"|"$)/,'')]]
781
+ binds = [[nil, table]]
742
782
  binds << [nil, schema] if schema
743
783
 
744
784
  exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
745
785
  SELECT COUNT(*)
746
- FROM pg_tables
747
- WHERE tablename = $1
748
- AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"}
786
+ FROM pg_class c
787
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
788
+ WHERE c.relkind in ('v','r')
789
+ AND c.relname = $1
790
+ AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
749
791
  SQL
750
792
  end
751
793
 
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]
794
+ # Returns true if schema exists.
795
+ def schema_exists?(name)
796
+ exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0
797
+ SELECT COUNT(*)
798
+ FROM pg_namespace
799
+ WHERE nspname = $1
800
+ SQL
766
801
  end
767
802
 
768
803
  # Returns an array of indexes for the given table.
769
804
  def indexes(table_name, name = nil)
770
805
  result = query(<<-SQL, name)
771
- SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
806
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
772
807
  FROM pg_class t
773
808
  INNER JOIN pg_index d ON t.oid = d.indrelid
774
809
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -784,7 +819,8 @@ module ActiveRecord
784
819
  index_name = row[0]
785
820
  unique = row[1] == 't'
786
821
  indkey = row[2].split(" ")
787
- oid = row[3]
822
+ inddef = row[3]
823
+ oid = row[4]
788
824
 
789
825
  columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
790
826
  SELECT a.attnum, a.attname
@@ -794,7 +830,12 @@ module ActiveRecord
794
830
  SQL
795
831
 
796
832
  column_names = columns.values_at(*indkey).compact
797
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
833
+
834
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
835
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
836
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
837
+
838
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
798
839
  end.compact
799
840
  end
800
841
 
@@ -811,6 +852,11 @@ module ActiveRecord
811
852
  query('select current_database()')[0][0]
812
853
  end
813
854
 
855
+ # Returns the current schema name.
856
+ def current_schema
857
+ query('SELECT current_schema', 'SCHEMA')[0][0]
858
+ end
859
+
814
860
  # Returns the current database encoding format.
815
861
  def encoding
816
862
  query(<<-end_sql)[0][0]
@@ -833,7 +879,7 @@ module ActiveRecord
833
879
 
834
880
  # Returns the active schema search path.
835
881
  def schema_search_path
836
- @schema_search_path ||= query('SHOW search_path')[0][0]
882
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
837
883
  end
838
884
 
839
885
  # Returns the current client message level.
@@ -874,7 +920,7 @@ module ActiveRecord
874
920
  end
875
921
 
876
922
  if pk && sequence
877
- quoted_sequence = quote_column_name(sequence)
923
+ quoted_sequence = quote_table_name(sequence)
878
924
 
879
925
  select_value <<-end_sql, 'Reset sequence'
880
926
  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)
@@ -886,46 +932,26 @@ module ActiveRecord
886
932
  def pk_and_sequence_for(table) #:nodoc:
887
933
  # First try looking for a sequence with a dependency on the
888
934
  # given table's primary key.
889
- result = query(<<-end_sql, 'PK and serial sequence')[0]
890
- SELECT attr.attname, seq.relname
891
- FROM pg_class seq,
892
- pg_attribute attr,
893
- pg_depend dep,
894
- pg_namespace name,
895
- pg_constraint cons
896
- WHERE seq.oid = dep.objid
897
- AND seq.relkind = 'S'
898
- AND attr.attrelid = dep.refobjid
899
- AND attr.attnum = dep.refobjsubid
900
- AND attr.attrelid = cons.conrelid
901
- AND attr.attnum = cons.conkey[1]
902
- AND cons.contype = 'p'
903
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
935
+ result = exec_query(<<-end_sql, 'SCHEMA').rows.first
936
+ SELECT attr.attname, ns.nspname, seq.relname
937
+ FROM pg_class seq
938
+ INNER JOIN pg_depend dep ON seq.oid = dep.objid
939
+ INNER JOIN pg_attribute attr ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
940
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
941
+ INNER JOIN pg_namespace ns ON seq.relnamespace = ns.oid
942
+ WHERE seq.relkind = 'S'
943
+ AND cons.contype = 'p'
944
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
904
945
  end_sql
905
946
 
906
- 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]
911
- SELECT attr.attname,
912
- 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)
917
- END
918
- FROM pg_class t
919
- JOIN pg_attribute attr ON (t.oid = attrelid)
920
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
921
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
922
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
923
- AND cons.contype = 'p'
924
- AND def.adsrc ~* 'nextval'
925
- end_sql
947
+ # [primary_key, sequence]
948
+ if result.second == 'public' then
949
+ sequence = result.last
950
+ else
951
+ sequence = result.second+'.'+result.last
926
952
  end
927
953
 
928
- [result.first, result.last]
954
+ [result.first, sequence]
929
955
  rescue
930
956
  nil
931
957
  end
@@ -1037,6 +1063,25 @@ module ActiveRecord
1037
1063
  "DISTINCT #{columns}, #{order_columns * ', '}"
1038
1064
  end
1039
1065
 
1066
+ module Utils
1067
+ extend self
1068
+
1069
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
1070
+ # +schema_name+ is nil if not specified in +name+.
1071
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
1072
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
1073
+ #
1074
+ # * <tt>table_name</tt>
1075
+ # * <tt>"table.name"</tt>
1076
+ # * <tt>schema_name.table_name</tt>
1077
+ # * <tt>schema_name."table.name"</tt>
1078
+ # * <tt>"schema.name"."table name"</tt>
1079
+ def extract_schema_and_table(name)
1080
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
1081
+ [schema, table]
1082
+ end
1083
+ end
1084
+
1040
1085
  protected
1041
1086
  # Returns the version of the connected PostgreSQL server.
1042
1087
  def postgresql_version
@@ -1203,9 +1248,14 @@ module ActiveRecord
1203
1248
  end
1204
1249
  end
1205
1250
 
1206
- def table_definition
1207
- TableDefinition.new(self)
1208
- end
1251
+ def extract_table_ref_from_insert_sql(sql)
1252
+ sql[/into\s+([^\(]*).*values\s*\(/i]
1253
+ $1.strip if $1
1254
+ end
1255
+
1256
+ def table_definition
1257
+ TableDefinition.new(self)
1258
+ end
1209
1259
  end
1210
1260
  end
1211
1261
  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
@@ -1,8 +1,6 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_support/core_ext/kernel/requires'
3
2
  require 'active_record/connection_adapters/statement_pool'
4
3
  require 'active_support/core_ext/string/encoding'
5
- require 'arel/visitors/bind_visitor'
6
4
 
7
5
  module ActiveRecord
8
6
  module ConnectionAdapters #:nodoc:
@@ -86,25 +84,12 @@ module ActiveRecord
86
84
  end
87
85
  end
88
86
 
89
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
90
- include Arel::Visitors::BindVisitor
91
- end
92
-
93
87
  def initialize(connection, logger, config)
94
88
  super(connection, logger)
95
89
  @statements = StatementPool.new(@connection,
96
90
  config.fetch(:statement_limit) { 1000 })
97
91
  @config = config
98
- end
99
-
100
- def self.visitor_for(pool) # :nodoc:
101
- config = pool.spec.config
102
-
103
- if config.fetch(:prepared_statements) { true }
104
- Arel::Visitors::SQLite.new pool
105
- else
106
- BindSubstitution.new pool
107
- end
92
+ @visitor = Arel::Visitors::SQLite.new self
108
93
  end
109
94
 
110
95
  def adapter_name #:nodoc:
@@ -137,6 +122,11 @@ module ActiveRecord
137
122
  true
138
123
  end
139
124
 
125
+ # Returns true.
126
+ def supports_explain?
127
+ true
128
+ end
129
+
140
130
  def requires_reloading?
141
131
  true
142
132
  end
@@ -169,6 +159,10 @@ module ActiveRecord
169
159
  sqlite_version >= '3.1.0'
170
160
  end
171
161
 
162
+ def supports_index_sort_order?
163
+ sqlite_version >= '3.3.0'
164
+ end
165
+
172
166
  def native_database_types #:nodoc:
173
167
  {
174
168
  :primary_key => default_primary_key_type,
@@ -215,7 +209,7 @@ module ActiveRecord
215
209
 
216
210
  value = super
217
211
  if column.type == :string && value.encoding == Encoding::ASCII_8BIT
218
- logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
212
+ @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
219
213
  value.encode! 'utf-8'
220
214
  end
221
215
  value
@@ -230,6 +224,25 @@ module ActiveRecord
230
224
 
231
225
  # DATABASE STATEMENTS ======================================
232
226
 
227
+ def explain(arel, binds = [])
228
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
229
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
230
+ end
231
+
232
+ class ExplainPrettyPrinter
233
+ # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
234
+ # the output of the SQLite shell:
235
+ #
236
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
237
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
238
+ #
239
+ def pp(result) # :nodoc:
240
+ result.rows.map do |row|
241
+ row.join('|')
242
+ end.join("\n") + "\n"
243
+ end
244
+ end
245
+
233
246
  def exec_query(sql, name = nil, binds = [])
234
247
  log(sql, name, binds) do
235
248
 
@@ -303,31 +316,36 @@ module ActiveRecord
303
316
  end
304
317
 
305
318
  def begin_db_transaction #:nodoc:
306
- @connection.transaction
319
+ log('begin transaction',nil) { @connection.transaction }
307
320
  end
308
321
 
309
322
  def commit_db_transaction #:nodoc:
310
- @connection.commit
323
+ log('commit transaction',nil) { @connection.commit }
311
324
  end
312
325
 
313
326
  def rollback_db_transaction #:nodoc:
314
- @connection.rollback
327
+ log('rollback transaction',nil) { @connection.rollback }
315
328
  end
316
329
 
317
330
  # SCHEMA STATEMENTS ========================================
318
331
 
319
- def tables(name = 'SCHEMA') #:nodoc:
332
+ def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
320
333
  sql = <<-SQL
321
334
  SELECT name
322
335
  FROM sqlite_master
323
336
  WHERE type = 'table' AND NOT name = 'sqlite_sequence'
324
337
  SQL
338
+ sql << " AND name = #{quote_table_name(table_name)}" if table_name
325
339
 
326
340
  exec_query(sql, name).map do |row|
327
341
  row['name']
328
342
  end
329
343
  end
330
344
 
345
+ def table_exists?(name)
346
+ name && tables('SCHEMA', name).any?
347
+ end
348
+
331
349
  # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
332
350
  def columns(table_name, name = nil) #:nodoc:
333
351
  table_structure(table_name).map do |field|
@@ -393,7 +411,7 @@ module ActiveRecord
393
411
  end
394
412
 
395
413
  def remove_column(table_name, *column_names) #:nodoc:
396
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
414
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
397
415
  column_names.flatten.each do |column_name|
398
416
  alter_table(table_name) do |definition|
399
417
  definition.columns.delete(definition[column_name])
@@ -425,6 +443,8 @@ module ActiveRecord
425
443
  self.limit = options[:limit] if options.include?(:limit)
426
444
  self.default = options[:default] if include_default
427
445
  self.null = options[:null] if options.include?(:null)
446
+ self.precision = options[:precision] if options.include?(:precision)
447
+ self.scale = options[:scale] if options.include?(:scale)
428
448
  end
429
449
  end
430
450
  end
@@ -479,6 +499,7 @@ module ActiveRecord
479
499
 
480
500
  @definition.column(column_name, column.type,
481
501
  :limit => column.limit, :default => column.default,
502
+ :precision => column.precision, :scale => column.scale,
482
503
  :null => column.null)
483
504
  end
484
505
  @definition.primary_key(primary_key(from)) if primary_key(from)
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  # = Active Record Counter Cache
3
3
  module CounterCache
4
4
  # Resets one or more counter caches to their correct value using an SQL
5
- # count query. This is useful when adding new counter caches, or if the
5
+ # count query. This is useful when adding new counter caches, or if the
6
6
  # counter has been corrupted or modified directly by SQL.
7
7
  #
8
8
  # ==== Parameters
@@ -25,9 +25,10 @@ module ActiveRecord
25
25
  self.name
26
26
  end
27
27
 
28
+ foreign_key = has_many_association.foreign_key.to_s
28
29
  child_class = has_many_association.klass
29
30
  belongs_to = child_class.reflect_on_all_associations(:belongs_to)
30
- reflection = belongs_to.find { |e| e.class_name == expected_name }
31
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
31
32
  counter_name = reflection.counter_cache_column
32
33
 
33
34
  stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
@@ -65,7 +66,7 @@ module ActiveRecord
65
66
  # Post.update_counters [10, 15], :comment_count => 1
66
67
  # # Executes the following SQL:
67
68
  # # UPDATE posts
68
- # # SET comment_count = COALESCE(comment_count, 0) + 1,
69
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
69
70
  # # WHERE id IN (10, 15)
70
71
  def update_counters(id, counters)
71
72
  updates = counters.map do |counter_name, value|