sequel-pg-comment 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/Gemfile +3 -0
- data/Guardfile +15 -0
- data/LICENCE +674 -0
- data/README.md +209 -0
- data/Rakefile +39 -0
- data/lib/sequel/extensions/pg_comment.rb +73 -0
- data/lib/sequel/extensions/pg_comment/alter_table_generator_methods.rb +126 -0
- data/lib/sequel/extensions/pg_comment/create_table_generator_methods.rb +159 -0
- data/lib/sequel/extensions/pg_comment/database_methods.rb +175 -0
- data/lib/sequel/extensions/pg_comment/dataset_methods.rb +22 -0
- data/lib/sequel/extensions/pg_comment/sql_generator.rb +257 -0
- data/sequel-pg-comment.gemspec +36 -0
- data/spec/alter_table_comment_spec.rb +124 -0
- data/spec/comment_for_spec.rb +34 -0
- data/spec/comment_on_spec.rb +56 -0
- data/spec/create_join_table_comment_spec.rb +20 -0
- data/spec/create_table_comment_spec.rb +193 -0
- data/spec/create_view_comment_spec.rb +18 -0
- data/spec/extension_spec.rb +11 -0
- data/spec/normalise_comment_spec.rb +48 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/sql_generator_spec.rb +96 -0
- metadata +242 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
# Enhancements to the standard schema modification methods in a
|
3
|
+
# block-form `create_table` method, to support setting comments via the
|
4
|
+
# `:comment` option.
|
5
|
+
#
|
6
|
+
module Sequel::Extension::PgComment::CreateTableGeneratorMethods
|
7
|
+
# An array of all the comments that this generator has seen fit to
|
8
|
+
# create.
|
9
|
+
#
|
10
|
+
# @return [Array<SqlGenerator>]
|
11
|
+
#
|
12
|
+
attr_reader :comments
|
13
|
+
|
14
|
+
include Sequel::Extension::PgComment
|
15
|
+
|
16
|
+
# Enhanced version of the `column` table definition method, which
|
17
|
+
# supports setting a comment on the column.
|
18
|
+
#
|
19
|
+
# @option [String] :comment The comment to set on the column that is
|
20
|
+
# being defined.
|
21
|
+
#
|
22
|
+
def column(*args)
|
23
|
+
super
|
24
|
+
|
25
|
+
if args.last.is_a?(Hash) && args.last[:comment]
|
26
|
+
comments << SqlGenerator.create(:column, args.first, args.last[:comment])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Enhanced version of the `primary_key` table definition method, which
|
31
|
+
# supports setting a comment on either the column or constraint.
|
32
|
+
#
|
33
|
+
# If the primary key is composite (`name` is an array), then the comment
|
34
|
+
# will be placed on the index. Otherwise, the comment will be set
|
35
|
+
# on the column itself.
|
36
|
+
#
|
37
|
+
# @option [String] :comment The comment to set on the column or
|
38
|
+
# index that is being defined.
|
39
|
+
#
|
40
|
+
def primary_key(name, *args)
|
41
|
+
if args.last.is_a?(Hash) && args.last[:comment] and !name.is_a? Array
|
42
|
+
# The composite primary key case will be handled by the
|
43
|
+
# `composite_primary_key` method, so we don't have to deal with it
|
44
|
+
# here.
|
45
|
+
comments << SqlGenerator.create(:column, name, args.last[:comment])
|
46
|
+
end
|
47
|
+
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
# Enhanced version of the `composite_primary_key` table definition method,
|
52
|
+
# which supports setting a comment on the primary key index.
|
53
|
+
#
|
54
|
+
# @option [String] :comment The comment to set on the primary key index.
|
55
|
+
#
|
56
|
+
def composite_primary_key(columns, *args)
|
57
|
+
if args.last.is_a?(Hash) and args.last[:comment]
|
58
|
+
if args.last[:name]
|
59
|
+
comments << SqlGenerator.create(
|
60
|
+
:index,
|
61
|
+
args.last[:name],
|
62
|
+
args.last[:comment]
|
63
|
+
)
|
64
|
+
else
|
65
|
+
comments << PrefixSqlGenerator.new(:index, :_pkey, args.last[:comment])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
# Enhanced version of the `composite_foreign_key` table definition method,
|
73
|
+
# which supports setting a comment on the FK constraint.
|
74
|
+
#
|
75
|
+
# @option [String] :comment The comment to set on the foreign key constraint.
|
76
|
+
#
|
77
|
+
def composite_foreign_key(columns, opts)
|
78
|
+
if opts.is_a?(Hash) and opts[:comment] and opts[:table]
|
79
|
+
if opts[:name]
|
80
|
+
comments << SqlGenerator.create(
|
81
|
+
:constraint,
|
82
|
+
opts[:name],
|
83
|
+
opts[:comment]
|
84
|
+
)
|
85
|
+
else
|
86
|
+
comments << SqlGenerator.create(
|
87
|
+
:constraint,
|
88
|
+
"#{opts[:table]}_#{columns.first}_fkey".to_sym,
|
89
|
+
opts[:comment]
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
super
|
95
|
+
end
|
96
|
+
|
97
|
+
# Enhanced version of the `index` table definition method,
|
98
|
+
# which supports setting a comment on the index.
|
99
|
+
#
|
100
|
+
# @option [String] :comment The comment to set on the index that is being
|
101
|
+
# defined.
|
102
|
+
#
|
103
|
+
def index(columns, opts = OPTS)
|
104
|
+
if opts[:comment]
|
105
|
+
if opts[:name]
|
106
|
+
comments << SqlGenerator.create(:index, opts[:name], opts[:comment])
|
107
|
+
else
|
108
|
+
comments << PrefixSqlGenerator.new(
|
109
|
+
:index,
|
110
|
+
("_" + [columns].flatten.map(&:to_s).join('_') + "_index").to_sym,
|
111
|
+
opts[:comment]
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
# Enhanced version of the `unique` table definition method,
|
120
|
+
# which supports setting a comment on the unique index.
|
121
|
+
#
|
122
|
+
# @option [String] :comment The comment to set on the index that will be
|
123
|
+
# defined.
|
124
|
+
#
|
125
|
+
def unique(columns, opts = OPTS)
|
126
|
+
if opts[:comment]
|
127
|
+
if opts[:name]
|
128
|
+
comments << SqlGenerator.create(:index, opts[:name], opts[:comment])
|
129
|
+
else
|
130
|
+
comments << PrefixSqlGenerator.new(
|
131
|
+
:index,
|
132
|
+
("_" + [columns].flatten.map(&:to_s).join('_') + "_key").to_sym,
|
133
|
+
opts[:comment]
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
super
|
139
|
+
end
|
140
|
+
|
141
|
+
# Enhanced version of the `constraint` table definition method,
|
142
|
+
# which supports setting a comment on the constraint.
|
143
|
+
#
|
144
|
+
# @option [String] :comment The comment to set on the constraint that is
|
145
|
+
# being defined.
|
146
|
+
#
|
147
|
+
def constraint(name, *args, &block)
|
148
|
+
opts = name.is_a?(Hash) ? name : (args.last.is_a?(Hash) ? args.last : {})
|
149
|
+
|
150
|
+
if opts[:comment]
|
151
|
+
if name
|
152
|
+
comments << SqlGenerator.create(:constraint, name, opts[:comment])
|
153
|
+
else
|
154
|
+
raise RuntimeError,
|
155
|
+
"Setting comments on unnamed or check constraints is not supported"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# Support for setting and retrieving comments on all object types
|
2
|
+
# in a PostgreSQL database.
|
3
|
+
#
|
4
|
+
module Sequel::Extension::PgComment::DatabaseMethods
|
5
|
+
# Set the comment for a database object.
|
6
|
+
#
|
7
|
+
# @param type [#to_s] The type of object that you wish to comment on.
|
8
|
+
# This can either be a string or symbol. Any object type that PgSQL
|
9
|
+
# knows about should be fair game. The current list of object types
|
10
|
+
# that this plugin knows about (and hence will accept) is listed in
|
11
|
+
# the {Sequel::Extension::PgComment::OBJECT_TYPES} array.
|
12
|
+
#
|
13
|
+
# @param id [#to_s] The name of the object that you wish to comment on.
|
14
|
+
# For most types of object, this should be the literal name of the
|
15
|
+
# object. However, for columns in a table or view, you should separate
|
16
|
+
# the table/view name from the column name with a double underscore
|
17
|
+
# (ie `__`). This is the standard Sequel convention for such things.
|
18
|
+
#
|
19
|
+
# @param comment [String] The comment you wish to set for the database
|
20
|
+
# object.
|
21
|
+
#
|
22
|
+
# @see {Sequel::Extension::PgComment.normalise_comment} for details on
|
23
|
+
# how the comment string is interpreted.
|
24
|
+
#
|
25
|
+
def comment_on(type, id, comment)
|
26
|
+
gen = begin
|
27
|
+
Sequel::Extension::PgComment::SqlGenerator.create(type, id, comment)
|
28
|
+
rescue ArgumentError
|
29
|
+
raise ArgumentError,
|
30
|
+
"Invalid object type: #{type.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
execute(gen.generate)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Retrieve the comment for a database object.
|
37
|
+
#
|
38
|
+
# @param object [#to_s] The name of the database object to retrieve. For
|
39
|
+
# most objects, this should be the literal name of the object.
|
40
|
+
# However, for columns on tables and views, the name of the table/view
|
41
|
+
# should be a separated from the name of the column by a double
|
42
|
+
# underscore (ie `__`).
|
43
|
+
#
|
44
|
+
# @return [String, NilClass] The comment on the object, or `nil` if no
|
45
|
+
# comment has been defined for the object.
|
46
|
+
#
|
47
|
+
def comment_for(object)
|
48
|
+
object = object.to_s
|
49
|
+
|
50
|
+
if object.index("__")
|
51
|
+
tbl, col = object.split("__", 2)
|
52
|
+
|
53
|
+
execute("SELECT col_description(c.oid, a.attnum) " +
|
54
|
+
"FROM pg_class c JOIN pg_attribute a " +
|
55
|
+
"ON (c.oid=a.attrelid) " +
|
56
|
+
"WHERE c.relname=#{literal(tbl)} " +
|
57
|
+
"AND a.attname=#{literal(col)}"
|
58
|
+
)
|
59
|
+
else
|
60
|
+
execute("SELECT obj_description(#{literal(object.to_s)}::regclass, 'pg_class')")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# An enhanced form of the standard `create_table` method, which supports
|
65
|
+
# setting a comment in the `create_table` call when the `:comment` option
|
66
|
+
# is provided.
|
67
|
+
#
|
68
|
+
# @option [String] :comment The comment to set on the newly-created table.
|
69
|
+
#
|
70
|
+
# @see [Sequel::Database#create_table](http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-create_table)
|
71
|
+
#
|
72
|
+
def create_table(*args)
|
73
|
+
super
|
74
|
+
|
75
|
+
if args.last.is_a?(Hash) && args.last[:comment]
|
76
|
+
comment_on(:table, args.first, args.last[:comment])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#:nodoc:
|
81
|
+
# Enhanced version to support setting comments on objects created in a
|
82
|
+
# block-form `create_table` statement.
|
83
|
+
#
|
84
|
+
def create_table_generator(&block)
|
85
|
+
super do
|
86
|
+
extend Sequel::Extension::PgComment::CreateTableGeneratorMethods
|
87
|
+
@comments = []
|
88
|
+
instance_eval(&block) if block
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
#:nodoc:
|
93
|
+
# Enhanced version to support setting comments on objects created in a
|
94
|
+
# block-form `create_table` statement.
|
95
|
+
#
|
96
|
+
# If you're wondering why we override the
|
97
|
+
# create_table_indexes_from_generator method, rather than
|
98
|
+
# create_table_from_generator, it's because the indexes method runs last,
|
99
|
+
# and we can only create our comments after the objects we're commenting
|
100
|
+
# on have been created. We *could* set some comments in
|
101
|
+
# create_table_from_generator, and then set index comments in
|
102
|
+
# create_table_indexes_from_generator, but why override two methods when
|
103
|
+
# you can just override one to get the same net result?
|
104
|
+
#
|
105
|
+
def create_table_indexes_from_generator(name, generator, options)
|
106
|
+
super
|
107
|
+
|
108
|
+
generator.comments.each do |sql_gen|
|
109
|
+
if sql_gen.respond_to? :table_name
|
110
|
+
sql_gen.table_name = name
|
111
|
+
end
|
112
|
+
|
113
|
+
execute(sql_gen.generate)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#:nodoc:
|
118
|
+
# Enhanced version to support setting comments on objects created in a
|
119
|
+
# block-form `alter_table` statement.
|
120
|
+
#
|
121
|
+
def alter_table_generator(&block)
|
122
|
+
super do
|
123
|
+
extend Sequel::Extension::PgComment::AlterTableGeneratorMethods
|
124
|
+
@comments = []
|
125
|
+
instance_eval(&block) if block
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
#:nodoc:
|
130
|
+
# Enhanced version to support setting comments on objects created in a
|
131
|
+
# block-form `alter_table` statement.
|
132
|
+
#
|
133
|
+
def apply_alter_table_generator(name, generator)
|
134
|
+
super
|
135
|
+
|
136
|
+
generator.comments.each do |sql_gen|
|
137
|
+
if sql_gen.respond_to?(:table_name=)
|
138
|
+
sql_gen.table_name = name
|
139
|
+
end
|
140
|
+
|
141
|
+
execute(sql_gen.generate)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# An enhanced form of the standard `create_view` method, which supports
|
146
|
+
# setting a comment in the `create_view` call when the `:comment` option
|
147
|
+
# is provided.
|
148
|
+
#
|
149
|
+
# @option [String] :comment The comment to set on the newly-created view.
|
150
|
+
#
|
151
|
+
# @see [Sequel::Database#create_view](http://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-create_view)
|
152
|
+
#
|
153
|
+
def create_view(*args)
|
154
|
+
super
|
155
|
+
|
156
|
+
if args.last.is_a?(Hash) && args.last[:comment]
|
157
|
+
comment_on(:view, args.first, args.last[:comment])
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Quote an object name, handling the double underscore convention
|
164
|
+
# for separating a column name from its containing object.
|
165
|
+
#
|
166
|
+
def quote_comment_identifier(id)
|
167
|
+
id = id.to_s
|
168
|
+
if id.index("__")
|
169
|
+
tbl, col = id.split("__", 2)
|
170
|
+
quote_identifier(tbl) + "." + quote_identifier(col)
|
171
|
+
else
|
172
|
+
quote_identifier(id)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Support for retrieving column comments from a PostgreSQL database
|
2
|
+
# via a dataset. For example:
|
3
|
+
#
|
4
|
+
# DB[:foo_tbl].comment_for(:some_column)
|
5
|
+
#
|
6
|
+
# Will retrieve the comment for `foo_tbl.some_column`, if such a
|
7
|
+
# column exists.
|
8
|
+
#
|
9
|
+
module Sequel::Extension::PgComment::DatasetMethods
|
10
|
+
# Retrieve the comment for the column named `col` in the "primary" table
|
11
|
+
# for this dataset.
|
12
|
+
#
|
13
|
+
# @param col [#to_s] The name of the column for which to retrieve the
|
14
|
+
# comment.
|
15
|
+
#
|
16
|
+
# @return [String, NilClass] The comment defined for the column, or
|
17
|
+
# `nil` if there is no defined comment.
|
18
|
+
#
|
19
|
+
def comment_for(col)
|
20
|
+
db.comment_for("#{first_source_table}__#{col}")
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
module Sequel::Extension::PgComment
|
2
|
+
#:nodoc:
|
3
|
+
# Generate SQL to set a comment.
|
4
|
+
#
|
5
|
+
class SqlGenerator
|
6
|
+
# The PostgreSQL object types which this class knows how to generate
|
7
|
+
# comment SQL for.
|
8
|
+
#
|
9
|
+
OBJECT_TYPES = %w{AGGREGATE
|
10
|
+
CAST
|
11
|
+
COLLATION
|
12
|
+
CONVERSION
|
13
|
+
DATABASE
|
14
|
+
DOMAIN
|
15
|
+
EXTENSION
|
16
|
+
EVENT\ TRIGGER
|
17
|
+
FOREIGN\ DATA\ WRAPPER
|
18
|
+
FOREIGN\ TABLE
|
19
|
+
FUNCTION
|
20
|
+
INDEX
|
21
|
+
LARGE\ OBJECT
|
22
|
+
MATERIALIZED\ VIEW
|
23
|
+
OPERATOR
|
24
|
+
OPERATOR\ CLASS
|
25
|
+
OPERATOR\ FAMILY
|
26
|
+
PROCEDURAL\ LANGUAGE
|
27
|
+
LANGUAGE
|
28
|
+
ROLE
|
29
|
+
SCHEMA
|
30
|
+
SEQUENCE
|
31
|
+
SERVER
|
32
|
+
TABLE
|
33
|
+
TABLESPACE
|
34
|
+
TEXT\ SEARCH\ CONFIGURATION
|
35
|
+
TEXT\ SEARCH\ DICTIONARY
|
36
|
+
TEXT\ SEARCH\ PARSER
|
37
|
+
TEXT\ SEARCH\ TEMPLATE
|
38
|
+
TYPE
|
39
|
+
VIEW
|
40
|
+
}
|
41
|
+
|
42
|
+
# Find the correct class for a given object type, and instantiate a
|
43
|
+
# new one of them.
|
44
|
+
#
|
45
|
+
# @param object_type [String, Symbol] The type of object we're going
|
46
|
+
# to comment on. Strings and symbols are both fine, and any case
|
47
|
+
# is fine, too. Any underscores get turned into spaces. Apart from
|
48
|
+
# that, it needs to be the exact name that PostgreSQL uses for the given
|
49
|
+
# type.
|
50
|
+
#
|
51
|
+
# @param object_name [String, Symbol] The name of the database object to
|
52
|
+
# set the comment on. A string is considered "already quoted", and hence
|
53
|
+
# is not escaped any further. A symbol is run through the usual Sequel
|
54
|
+
# identifier escaping code before being unleashed on the world.
|
55
|
+
#
|
56
|
+
# @param comment [String] The comment to set.
|
57
|
+
#
|
58
|
+
# @return [SqlGenerator] Some sort of `SqlGenerator` object, or a subclass.
|
59
|
+
#
|
60
|
+
# @raise [ArgumentError] if you passed in an `object_type` that we don't
|
61
|
+
# know about.
|
62
|
+
#
|
63
|
+
def self.create(object_type, object_name, comment)
|
64
|
+
generators.each do |gclass|
|
65
|
+
if gclass.handles?(object_type)
|
66
|
+
return gclass.new(object_type, object_name, comment)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
raise ArgumentError,
|
71
|
+
"Unrecognised object type #{object_type.inspect}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Return whether or not this class supports the specified object type.
|
75
|
+
#
|
76
|
+
# @param object_type [String, Symbol] @see {.create}
|
77
|
+
#
|
78
|
+
# @return [TrueClass, FalseClass] whether or not this class can handle
|
79
|
+
# the object type you passed.
|
80
|
+
#
|
81
|
+
def self.handles?(object_type)
|
82
|
+
self.const_get(:OBJECT_TYPES).include?(object_type.to_s.upcase.gsub('_', ' '))
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Return all known `SqlGenerator` classes.
|
88
|
+
#
|
89
|
+
def self.generators
|
90
|
+
@generators ||= ObjectSpace.each_object(Class).select do |klass|
|
91
|
+
klass.ancestors.include?(self)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# We just need this so we can quote things.
|
96
|
+
def self.mock_db
|
97
|
+
@mock_db ||= Sequel.connect("mock://postgres")
|
98
|
+
end
|
99
|
+
|
100
|
+
public
|
101
|
+
|
102
|
+
# The canonicalised string (that is, all-uppercase, with words
|
103
|
+
# separated by spaces) for the object type of this SQL generator.
|
104
|
+
#
|
105
|
+
attr_reader :object_type
|
106
|
+
|
107
|
+
# The raw (might-be-a-symbol, might-be-a-string) object name that
|
108
|
+
# was passed to us originally.
|
109
|
+
#
|
110
|
+
attr_reader :object_name
|
111
|
+
|
112
|
+
# The comment.
|
113
|
+
attr_reader :comment
|
114
|
+
|
115
|
+
# Spawn a new SqlGenerator.
|
116
|
+
#
|
117
|
+
# @see {.create}
|
118
|
+
#
|
119
|
+
def initialize(object_type, object_name, comment)
|
120
|
+
@object_type = object_type.to_s.upcase.gsub('_', ' ')
|
121
|
+
@object_name = object_name
|
122
|
+
@comment = comment
|
123
|
+
end
|
124
|
+
|
125
|
+
# SQL to set a comment on the object of our affection.
|
126
|
+
#
|
127
|
+
# @return [String] The SQL needed to set the comment.
|
128
|
+
#
|
129
|
+
def generate
|
130
|
+
quoted_object_name = case object_name
|
131
|
+
when Symbol
|
132
|
+
literal object_name
|
133
|
+
else
|
134
|
+
object_name
|
135
|
+
end
|
136
|
+
|
137
|
+
"COMMENT ON #{object_type} #{quoted_object_name} IS #{literal comment.to_s}"
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# Quote the provided database object (a symbol) or string value
|
143
|
+
# (a string).
|
144
|
+
#
|
145
|
+
def literal(s)
|
146
|
+
self.class.mock_db.literal(s)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#:nodoc:
|
151
|
+
# A specialised generator for object types that live "inside" a
|
152
|
+
# table. Specifically, those types are columns, constraints,
|
153
|
+
# rules, and triggers.
|
154
|
+
#
|
155
|
+
# They get their own subclass because these object types can be
|
156
|
+
# manipulated inside a `create_table` or `alter_table` block, and at the
|
157
|
+
# time the block is evaluated, the code doesn't know the name of the
|
158
|
+
# table in which they are contained. So, we just stuff what we *do* know
|
159
|
+
# into these generators, and then when all's done, we can go to each of
|
160
|
+
# these generators, say "this is your table name", and then ask for the
|
161
|
+
# generated SQL.
|
162
|
+
#
|
163
|
+
class TableObjectSqlGenerator < SqlGenerator
|
164
|
+
# The few object types that this class handles.
|
165
|
+
OBJECT_TYPES = %w{COLUMN CONSTRAINT RULE TRIGGER}
|
166
|
+
|
167
|
+
# The name of the object which contains the object which is the direct
|
168
|
+
# target of this SQL generator. Basically, it's the table name.
|
169
|
+
attr_accessor :table_name
|
170
|
+
|
171
|
+
# Overridden constructor to deal with the double-underscore-separated
|
172
|
+
# names that we all know and love.
|
173
|
+
#
|
174
|
+
# @see {SqlGenerator#initialize}
|
175
|
+
#
|
176
|
+
def initialize(object_type, object_name, comment)
|
177
|
+
super
|
178
|
+
|
179
|
+
if object_name.is_a?(Symbol) and object_name.to_s.index("__")
|
180
|
+
@table_name, @object_name = object_name.to_s.split("__", 2).map(&:to_sym)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Generate special SQL.
|
185
|
+
#
|
186
|
+
# @see {SqlGenerator#generate}
|
187
|
+
#
|
188
|
+
def generate
|
189
|
+
if table_name.nil?
|
190
|
+
raise ArgumentError,
|
191
|
+
"Cannot generate SQL for #{object_type} #{object_name} " +
|
192
|
+
"without a table_name"
|
193
|
+
end
|
194
|
+
|
195
|
+
qualified_object_name = case object_type
|
196
|
+
when "COLUMN"
|
197
|
+
"#{maybe_escape table_name}.#{maybe_escape object_name}"
|
198
|
+
when "CONSTRAINT", "RULE", "TRIGGER"
|
199
|
+
"#{maybe_escape object_name} ON #{maybe_escape table_name}"
|
200
|
+
end
|
201
|
+
|
202
|
+
"COMMENT ON #{object_type} #{qualified_object_name} IS #{literal comment}"
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# Handle with the vagaries of having both strings and symbols as
|
208
|
+
# possible names -- we escape symbols, but leave strings to their own
|
209
|
+
# devices.
|
210
|
+
#
|
211
|
+
def maybe_escape(s)
|
212
|
+
Symbol === s ? literal(s) : s
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#:nodoc:
|
217
|
+
# This is an annoying corner-case generator -- it doesn't handle any
|
218
|
+
# types by default, but it will handle any *other* type where the name of
|
219
|
+
# a table needs to be prefixed by a name. The only known use case for
|
220
|
+
# this at present is "implicit" (that is, automatically generated by the
|
221
|
+
# database) constraints and indexes that get prefixed by the table name,
|
222
|
+
# and which are generated at a time when the calling code doesn't know the
|
223
|
+
# name of the table that it is generating SQL for.
|
224
|
+
#
|
225
|
+
class PrefixSqlGenerator < SqlGenerator
|
226
|
+
# This class doesn't handle any object types directly, and must be
|
227
|
+
# instantiated directly when needed
|
228
|
+
OBJECT_TYPES = %w{}
|
229
|
+
|
230
|
+
# The name of the table which should be prefixed to the object name
|
231
|
+
# that was specified when this instance was created.
|
232
|
+
#
|
233
|
+
attr_accessor :table_name
|
234
|
+
|
235
|
+
# Generate super-dooper special SQL.
|
236
|
+
#
|
237
|
+
# @see {SqlGenerator#generate}
|
238
|
+
#
|
239
|
+
def generate
|
240
|
+
if table_name.nil?
|
241
|
+
raise ArgumentError,
|
242
|
+
"Cannot generate SQL for #{object_type} #{object_name} " +
|
243
|
+
"without a table_name"
|
244
|
+
end
|
245
|
+
|
246
|
+
prefixed_object_name = "#{table_name}#{object_name}"
|
247
|
+
|
248
|
+
if Symbol === table_name || Symbol === object_name
|
249
|
+
prefixed_object_name = prefixed_object_name.to_sym
|
250
|
+
end
|
251
|
+
|
252
|
+
g = SqlGenerator.create(object_type, prefixed_object_name, comment)
|
253
|
+
g.table_name = table_name if g.respond_to? :table_name
|
254
|
+
g.generate
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|