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.
- checksums.yaml +6 -6
- data/CHANGELOG.md +317 -336
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +3 -42
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +64 -31
- data/lib/active_record/associations/collection_proxy.rb +2 -37
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +28 -9
- data/lib/active_record/associations/has_one_association.rb +15 -13
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +7 -3
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +102 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
- data/lib/active_record/attribute_methods/write.rb +31 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +43 -22
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +56 -85
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +30 -25
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +31 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +191 -110
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +47 -15
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +9 -7
- data/lib/active_record/relation/predicate_builder.rb +18 -7
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +11 -2
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +17 -15
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +7 -7
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +30 -10
- 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'(.*)'
|
|
148
|
+
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
|
|
150
149
|
$1
|
|
151
|
-
# Character types (8.1 formatting)
|
|
152
|
-
when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
|
|
153
|
-
$1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
|
|
154
150
|
# Binary data types
|
|
155
151
|
when /\A'(.*)'::bytea\z/m
|
|
156
152
|
$1
|
|
@@ -201,7 +197,7 @@ module ActiveRecord
|
|
|
201
197
|
# * <tt>:password</tt> - Defaults to nothing.
|
|
202
198
|
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
|
203
199
|
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
|
|
204
|
-
# as a string of comma-separated schema names.
|
|
200
|
+
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
|
|
205
201
|
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
|
|
206
202
|
# <encoding></tt> call on the connection.
|
|
207
203
|
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
|
|
@@ -249,6 +245,10 @@ module ActiveRecord
|
|
|
249
245
|
true
|
|
250
246
|
end
|
|
251
247
|
|
|
248
|
+
def supports_index_sort_order?
|
|
249
|
+
true
|
|
250
|
+
end
|
|
251
|
+
|
|
252
252
|
class StatementPool < ConnectionAdapters::StatementPool
|
|
253
253
|
def initialize(connection, max)
|
|
254
254
|
super
|
|
@@ -309,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.
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
pk = primary_key(
|
|
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
|
-
|
|
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.
|
|
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
|
|
790
|
+
schema, table = Utils.extract_schema_and_table(name.to_s)
|
|
791
|
+
return false unless table
|
|
740
792
|
|
|
741
|
-
binds = [[nil, table
|
|
793
|
+
binds = [[nil, table]]
|
|
742
794
|
binds << [nil, schema] if schema
|
|
743
795
|
|
|
744
|
-
exec_query(<<-SQL, 'SCHEMA'
|
|
796
|
+
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
745
797
|
SELECT COUNT(*)
|
|
746
|
-
FROM
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
#
|
|
753
|
-
def
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
end
|
|
760
|
-
|
|
761
|
-
if name =~ /^"/ # Handle quoted table names
|
|
762
|
-
table = name
|
|
763
|
-
schema = nil
|
|
764
|
-
end
|
|
765
|
-
[schema, table]
|
|
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,
|
|
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
|
-
|
|
834
|
+
inddef = row[3]
|
|
835
|
+
oid = row[4]
|
|
788
836
|
|
|
789
|
-
columns = Hash[query(<<-SQL, "
|
|
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
|
-
|
|
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
|
|
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'
|
|
858
|
-
SELECT pg_get_serial_sequence(
|
|
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 =
|
|
935
|
+
quoted_sequence = quote_table_name(sequence)
|
|
878
936
|
|
|
879
|
-
select_value <<-end_sql, '
|
|
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, '
|
|
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
|
-
|
|
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.
|
|
914
|
-
substr(split_part(def.
|
|
915
|
-
strpos(split_part(def.
|
|
916
|
-
ELSE split_part(def.
|
|
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.
|
|
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'
|
|
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 =
|
|
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
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
when
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
|
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(
|
|
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),
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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.
|
|
3
|
+
gem 'sqlite3', '~> 1.3.5'
|
|
4
4
|
require 'sqlite3'
|
|
5
5
|
|
|
6
6
|
module ActiveRecord
|
|
@@ -47,11 +47,7 @@ module ActiveRecord
|
|
|
47
47
|
|
|
48
48
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
|
49
49
|
def encoding
|
|
50
|
-
|
|
51
|
-
@connection.encoding.to_s
|
|
52
|
-
else
|
|
53
|
-
@connection.execute('PRAGMA encoding')[0]['encoding']
|
|
54
|
-
end
|
|
50
|
+
@connection.encoding.to_s
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
end
|