Skip to content

Commit 4246fc3

Browse files
author
Keith Gabryelski
committed
support redshift (see activerecord-redshift-adapter)
1 parent 4ac6592 commit 4246fc3

File tree

6 files changed

+390
-2
lines changed

6 files changed

+390
-2
lines changed

lib/monkey_patch_postgres.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def check_constraint(constraint)
2929
# to take advantage of these SQL builders.
3030
#
3131
class PostgreSQLAdapter < AbstractAdapter
32+
def partitioned_sql_adapter(model)
33+
return Partitioned::PartitionedBase::SqlAdapter.new(model)
34+
end
3235

3336
#
3437
# Returns the sequence name for a table's primary key or some other specified key.

lib/monkey_patch_redshift.rb

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
require 'active_record'
2+
require 'active_record/base'
3+
require 'active_record/connection_adapters/abstract_adapter'
4+
require 'active_record/connection_adapters/redshift_adapter'
5+
6+
#
7+
# Patching {ActiveRecord::ConnectionAdapters::TableDefinition} and
8+
# {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter} to add functionality
9+
# needed to abstract partition specific SQL statements.
10+
#
11+
module ActiveRecord::ConnectionAdapters
12+
#
13+
# Patches extending the postgres adapter with new operations for managing
14+
# sequences (and sets of sequence values), schemas and foreign keys.
15+
# These should go into AbstractAdapter allowing any database adapter
16+
# to take advantage of these SQL builders.
17+
#
18+
class RedshiftAdapter < AbstractAdapter
19+
def partitioned_sql_adapter(model)
20+
return Partitioned::PartitionedBase::RedshiftSqlAdapter.new(model)
21+
end
22+
23+
#
24+
# Returns the sequence name for a table's primary key or some other specified key.
25+
#
26+
# the default version strips off the schema name on the table (if it exists), as:
27+
# serial_sequence(table_name, pk || 'id').split('.').last
28+
# i can't see any good reason for that -- in fact, it seems completely
29+
# broken -- if you have a table public.foos and other.foos, you'll fail to
30+
# get the correct schema if you fetch the default schema name from model
31+
# associated with other.foos
32+
#
33+
def default_sequence_name(table_name, pk = nil) #:nodoc:
34+
serial_sequence(table_name, pk || 'id')
35+
rescue ActiveRecord::StatementInvalid => e
36+
"#{table_name}_#{pk || 'id'}_seq"
37+
end
38+
39+
#
40+
# Get the next value in a sequence. Used on INSERT operation for
41+
# partitioning like by_id because the ID is required before the insert
42+
# so that the specific child table is known ahead of time.
43+
#
44+
# @param [String] sequence_name the name of the sequence to fetch the next value from
45+
# @return [Integer] the value from the sequence
46+
def next_sequence_value(sequence_name)
47+
return execute("select nextval('#{sequence_name}')").field_values("nextval").first.to_i
48+
end
49+
50+
#
51+
# Get the some next values in a sequence.
52+
#
53+
# @param [String] sequence_name the name of the sequence to fetch the next values from
54+
# @param [Integer] batch_size count of values.
55+
# @return [Array<Integer>] an array of values from the sequence
56+
def next_sequence_values(sequence_name, batch_size)
57+
result = execute("select nextval('#{sequence_name}') from generate_series(1, #{batch_size})")
58+
return result.field_values("nextval").map(&:to_i)
59+
end
60+
61+
#
62+
# Causes active resource to fetch the primary key for the table (using next_sequence_value())
63+
# just before an insert. We need the prefetch to happen but we don't have enough information
64+
# here to determine if it should happen, so Relation::insert has been modified to request of
65+
# the ActiveRecord::Base derived class if it requires a prefetch.
66+
#
67+
# @param [String] table_name the table name to query
68+
# @return [Boolean] returns true if the table should have its primary key prefetched.
69+
def prefetch_primary_key?(table_name)
70+
return false
71+
end
72+
73+
#
74+
# Creates a schema given a name.
75+
#
76+
# @param [String] name the name of the schema.
77+
# @param [Hash] options ({}) options for creating a schema
78+
# @option options [Boolean] :unless_exists (false) check if schema exists.
79+
# @return [optional] undefined
80+
def create_schema(name, options = {})
81+
if options[:unless_exists]
82+
return if execute("select count(*) from pg_namespace where nspname = '#{name}'").getvalue(0,0).to_i > 0
83+
end
84+
execute("CREATE SCHEMA #{name}")
85+
end
86+
87+
#
88+
# Drop a schema given a name.
89+
#
90+
# @param [String] name the name of the schema.
91+
# @param [Hash] options ({}) options for dropping a schema
92+
# @option options [Boolean] :if_exists (false) check if schema exists.
93+
# @option options [Boolean] :cascade (false) drop dependant objects
94+
# @return [optional] undefined
95+
def drop_schema(name, options = {})
96+
if options[:if_exists]
97+
return if execute("select count(*) from pg_namespace where nspname = '#{name}'").getvalue(0,0).to_i == 0
98+
end
99+
execute("DROP SCHEMA #{name}#{' cascade' if options[:cascade]}")
100+
end
101+
102+
#
103+
# Add foreign key constraint to table.
104+
#
105+
# @param [String] referencing_table_name the name of the table containing the foreign key
106+
# @param [String] referencing_field_name the name of foreign key column
107+
# @param [String] referenced_table_name the name of the table referenced by the foreign key
108+
# @param [String] referenced_field_name (:id) the name of the column referenced by the foreign key
109+
# @return [optional] undefined
110+
def add_foreign_key(referencing_table_name, referencing_field_name, referenced_table_name, referenced_field_name = :id)
111+
execute("ALTER TABLE #{referencing_table_name} add foreign key (#{referencing_field_name}) references #{referenced_table_name}(#{referenced_field_name})")
112+
end
113+
end
114+
end

lib/partitioned.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'monkey_patch_activerecord'
22
require 'monkey_patch_postgres'
3+
require 'monkey_patch_redshift'
34

45
require 'partitioned/active_record_overrides'
56
require 'partitioned/partitioned_base/configurator.rb'
@@ -9,6 +10,7 @@
910
require 'partitioned/partitioned_base/configurator/reader'
1011
require 'partitioned/partitioned_base/partition_manager'
1112
require 'partitioned/partitioned_base/sql_adapter'
13+
require 'partitioned/partitioned_base/redshift_sql_adapter'
1214

1315
require 'partitioned/by_time_field'
1416
require 'partitioned/by_yearly_time_field'

lib/partitioned/partitioned_base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def self.partition_manager
103103
#
104104
# @return [{SqlAdapter}] the object used to create sql statements for this partitioned model
105105
def self.sql_adapter
106-
@sql_adapter = self::SqlAdapter.new(self) unless @sql_adapter.present?
106+
@sql_adapter ||= connection.partitioned_sql_adapter(self)
107107
return @sql_adapter
108108
end
109109

0 commit comments

Comments
 (0)