mkxms-mssql 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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/bin/mkxms-mssql +5 -0
- data/lib/mkxms/mssql/access_object_definition.rb +61 -0
- data/lib/mkxms/mssql/adoption_script_writer.rb +1486 -0
- data/lib/mkxms/mssql/check_constraint_handler.rb +56 -0
- data/lib/mkxms/mssql/database_handler.rb +339 -0
- data/lib/mkxms/mssql/default_constraint_handler.rb +46 -0
- data/lib/mkxms/mssql/engine.rb +88 -0
- data/lib/mkxms/mssql/exceptions.rb +10 -0
- data/lib/mkxms/mssql/filegroup_handler.rb +81 -0
- data/lib/mkxms/mssql/foreign_key_handler.rb +85 -0
- data/lib/mkxms/mssql/function_handler.rb +74 -0
- data/lib/mkxms/mssql/indented_string_builder.rb +199 -0
- data/lib/mkxms/mssql/index_column.rb +11 -0
- data/lib/mkxms/mssql/index_handler.rb +98 -0
- data/lib/mkxms/mssql/keylike_constraint_helper.rb +67 -0
- data/lib/mkxms/mssql/permission_handler.rb +115 -0
- data/lib/mkxms/mssql/primary_key_handler.rb +36 -0
- data/lib/mkxms/mssql/property_handler.rb +87 -0
- data/lib/mkxms/mssql/query_cursor.rb +111 -0
- data/lib/mkxms/mssql/role_handler.rb +55 -0
- data/lib/mkxms/mssql/schema_handler.rb +42 -0
- data/lib/mkxms/mssql/sql_string_manipulators.rb +46 -0
- data/lib/mkxms/mssql/statistics_handler.rb +59 -0
- data/lib/mkxms/mssql/stored_procedure_handler.rb +65 -0
- data/lib/mkxms/mssql/table_handler.rb +180 -0
- data/lib/mkxms/mssql/unique_constraint_handler.rb +32 -0
- data/lib/mkxms/mssql/utils.rb +83 -0
- data/lib/mkxms/mssql/version.rb +5 -0
- data/lib/mkxms/mssql/view_handler.rb +58 -0
- data/lib/mkxms/mssql.rb +62 -0
- data/mkxms-mssql.gemspec +26 -0
- data/spec/utils/indented_string_builder_spec.rb +218 -0
- data/spec/utils/query_cursor_spec.rb +57 -0
- metadata +142 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'mkxms/mssql/property_handler'
|
2
|
+
require 'mkxms/mssql/utils'
|
3
|
+
|
4
|
+
module Mkxms; end
|
5
|
+
|
6
|
+
module Mkxms::Mssql
|
7
|
+
class Index
|
8
|
+
extend Utils::FlagsQueries
|
9
|
+
include ExtendedProperties, Property::Hosting
|
10
|
+
|
11
|
+
def initialize(attrs)
|
12
|
+
@schema = attrs['schema']
|
13
|
+
@relation = attrs['relation']
|
14
|
+
@name = attrs['name']
|
15
|
+
@fill_factor = attrs['fill-factor']
|
16
|
+
@spatial_index_geometry = attrs['spatial-index-over']
|
17
|
+
@cells_per_object = attrs['cells-per-object']
|
18
|
+
@storage = attrs['stored-on']
|
19
|
+
@columns = []
|
20
|
+
@included_columns = []
|
21
|
+
|
22
|
+
@flags = []
|
23
|
+
@flags << :unique if attrs['unique']
|
24
|
+
@flags << :padded if attrs['padded']
|
25
|
+
@flags << :disabled if attrs['disabled']
|
26
|
+
@flags << :ignore_duplicates if attrs['ignore-duplicates']
|
27
|
+
@flags << :row_locks_prohibited if attrs['no-row-locks']
|
28
|
+
@flags << :page_locks_prohibited if attrs['no-page-locks']
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :schema, :relation, :name, :fill_factor, :spatial_index_geometry, :cells_per_object, :storage
|
32
|
+
attr_reader :columns, :included_columns, :flags
|
33
|
+
|
34
|
+
flags_query :unique, :padded, :ignore_duplicates, :row_locks_prohibited, :page_locks_prohibited
|
35
|
+
|
36
|
+
def to_sql
|
37
|
+
if @spatial_index_geometry
|
38
|
+
else
|
39
|
+
[].tap do |parts|
|
40
|
+
parts << "CREATE #{'UNIQUE ' if unique?}INDEX #@name ON #{qualified_relation} (\n" +
|
41
|
+
@columns.map(&:to_sql).join(', ') +
|
42
|
+
"\n)"
|
43
|
+
|
44
|
+
parts << "INCLUDE (\n" +
|
45
|
+
@included_columns.map(&:name).join(', ') +
|
46
|
+
"\n)" unless @included_columns.empty?
|
47
|
+
|
48
|
+
# TODO: "WHERE" clause
|
49
|
+
|
50
|
+
options = []
|
51
|
+
options << "PAD_INDEX = ON" if padded?
|
52
|
+
options << "FILLFACTOR = #@fill_factor" if @fill_factor
|
53
|
+
options << "IGNORE_DUP_KEY = ON" if ignore_duplicates?
|
54
|
+
options << "ALLOW_ROW_LOCKS = OFF" if row_locks_prohibited?
|
55
|
+
options << "ALLOW_PAGE_LOCKS = OFF" if page_locks_prohibited?
|
56
|
+
parts << "WITH (#{options.join(', ')})" unless options.empty?
|
57
|
+
|
58
|
+
parts << "ON #@storage" if @storage
|
59
|
+
|
60
|
+
end.join(' ') + ';' + extended_properties_sql.joined_on_new_lines
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def property_subject_identifiers
|
65
|
+
['SCHEMA', @schema, 'TABLE', @relation, 'INDEX', @name].map {|n| Utils.unquoted_name(n)}
|
66
|
+
end
|
67
|
+
|
68
|
+
def qualified_relation
|
69
|
+
[@schema, @relation].join '.'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class IndexHandler
|
74
|
+
include PropertyHandler::ElementHandler
|
75
|
+
|
76
|
+
def initialize(indexes, node)
|
77
|
+
@index = Index.new(node.attributes).tap do |i|
|
78
|
+
indexes << i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def extended_properties
|
83
|
+
@index.extended_properties
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_column_element(parse)
|
87
|
+
a = parse.node.attributes
|
88
|
+
|
89
|
+
if a['included']
|
90
|
+
@index.included_columns << IndexColumn.new(a['name'])
|
91
|
+
else
|
92
|
+
@index.columns << IndexColumn.new(a['name'], a['desc'] ? :descending : :ascending)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# TODO: Handle partition columns
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'mkxms/mssql/index_column'
|
2
|
+
require 'mkxms/mssql/property_handler'
|
3
|
+
require 'mkxms/mssql/utils'
|
4
|
+
|
5
|
+
module Mkxms; end
|
6
|
+
|
7
|
+
module Mkxms::Mssql
|
8
|
+
class KeylikeConstraint
|
9
|
+
extend Utils::FlagsQueries
|
10
|
+
include ExtendedProperties, Property::Hosting
|
11
|
+
|
12
|
+
def initialize(attrs)
|
13
|
+
@schema = attrs['schema']
|
14
|
+
@table = attrs['table']
|
15
|
+
@name = attrs['name']
|
16
|
+
@stored_on = attrs['stored-on']
|
17
|
+
@fill_factor = attrs['fill-factor']
|
18
|
+
|
19
|
+
@flags = []
|
20
|
+
@flags << :clustered if attrs['clustered']
|
21
|
+
@flags << :paddedd if attrs['padded']
|
22
|
+
@flags << :row_locks_ok unless attrs['no-row-locks']
|
23
|
+
@flags << :page_locks_ok unless attrs['no-page-locks']
|
24
|
+
|
25
|
+
@columns = []
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :schema, :table, :name, :stored_on, :fill_factor
|
29
|
+
attr_reader :columns, :flags
|
30
|
+
flags_query :clustered, :padded, :row_locks_ok, :page_locks_ok
|
31
|
+
|
32
|
+
def to_sql
|
33
|
+
"ALTER TABLE #@schema.#@table ADD #{"CONSTRAINT #@name " if @name}" +
|
34
|
+
"#{self.sql_constraint_type} #{'NON' unless clustered?}CLUSTERED (\n" +
|
35
|
+
' ' + columns.map {|c| c.to_sql}.join(", ") +
|
36
|
+
"\n)" +
|
37
|
+
with_clause_sql +
|
38
|
+
# TODO: Handle partitioned constraints
|
39
|
+
"#{" ON #@stored_on" if @stored_on}" +
|
40
|
+
";" +
|
41
|
+
(name ? extended_properties_sql.joined_on_new_lines : '')
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_clause_sql
|
45
|
+
options = []
|
46
|
+
options << 'PAD_INDEX = ON' if padded?
|
47
|
+
options << "FILLFACTOR = #@fill_factor" if fill_factor
|
48
|
+
options << 'ALLOW_ROW_LOCKS = OFF' unless row_locks_ok?
|
49
|
+
options << 'ALLOW_PAGE_LOCKS = OFF' unless page_locks_ok?
|
50
|
+
|
51
|
+
return '' if options.empty?
|
52
|
+
return " WITH (\n#{options.join ", "}\n)"
|
53
|
+
end
|
54
|
+
|
55
|
+
def qualified_table
|
56
|
+
"#@schema.#@table"
|
57
|
+
end
|
58
|
+
|
59
|
+
def qualified_name
|
60
|
+
"#@schema.#@name" if @name
|
61
|
+
end
|
62
|
+
|
63
|
+
def property_subject_identifiers
|
64
|
+
@prop_subj_id ||= ['SCHEMA', schema, 'TABLE', table, 'CONSTRAINT', name].map {|s| Utils::unquoted_name(s)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'mkxms/mssql/utils'
|
2
|
+
|
3
|
+
module Mkxms; end
|
4
|
+
|
5
|
+
module Mkxms::Mssql
|
6
|
+
class PermissionGroup
|
7
|
+
ACTION_STATEMENT_PROLOG_TEMPLATES = {
|
8
|
+
'granted' => 'GRANT %s ON %s TO %s',
|
9
|
+
'denied' => 'DENY %s ON %s TO %s',
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(action, subject)
|
13
|
+
@action = action
|
14
|
+
@subject = subject
|
15
|
+
@permissions = []
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :action, :subject
|
19
|
+
attr_reader :permissions
|
20
|
+
|
21
|
+
def super_permissions_sql
|
22
|
+
super_permissions.map do |p|
|
23
|
+
''.tap do |sql|
|
24
|
+
sql << ACTION_STATEMENT_PROLOG_TEMPLATES[action] % [p.name, p.target, subject]
|
25
|
+
sql << ' WITH GRANT OPTION' if p.grant_option?
|
26
|
+
sql << ';'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def regular_permissions_graph
|
32
|
+
Hash.new.tap do |result|
|
33
|
+
regular_permissions.sort {|a, b| a.target <=> b.target}.group_by {|p| p.target}.each_pair do |target, perms|
|
34
|
+
result[target] = perms.map(&:name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_super_permission?(p)
|
40
|
+
action != 'granted' || p.grant_option?
|
41
|
+
end
|
42
|
+
|
43
|
+
def super_permissions
|
44
|
+
permissions.select {|p| is_super_permission? p}
|
45
|
+
end
|
46
|
+
|
47
|
+
def regular_permissions
|
48
|
+
permissions.select {|p| !is_super_permission? p}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Permission
|
53
|
+
def initialize(attrs)
|
54
|
+
@name = attrs['name']
|
55
|
+
@target_type = attrs['target-type']
|
56
|
+
@name_scope = attrs['name-scope']
|
57
|
+
@schema = attrs['in-schema']
|
58
|
+
@object = attrs['on']
|
59
|
+
@column = attrs['column']
|
60
|
+
@target = if @object
|
61
|
+
"".tap do |subject|
|
62
|
+
if @schema
|
63
|
+
subject << (@schema + '.')
|
64
|
+
end
|
65
|
+
subject << @object
|
66
|
+
subject << " (#@column)" if @column
|
67
|
+
end
|
68
|
+
else
|
69
|
+
'DATABASE'
|
70
|
+
end
|
71
|
+
@grant_option = attrs['with-grant-option']
|
72
|
+
@authority = attrs['by']
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_accessor :name, :target_type, :name_scope, :column, :authority
|
76
|
+
|
77
|
+
def target(scoped: true)
|
78
|
+
if scoped && @name_scope
|
79
|
+
"#@name_scope :: #@target"
|
80
|
+
else
|
81
|
+
@target
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def unscoped_target
|
86
|
+
target(scoped: false)
|
87
|
+
end
|
88
|
+
|
89
|
+
def grant_option?
|
90
|
+
@grant_option
|
91
|
+
end
|
92
|
+
def grant_option=(value)
|
93
|
+
@grant_option = value
|
94
|
+
end
|
95
|
+
|
96
|
+
def object_id_parts
|
97
|
+
[@target_type, @schema, @object, @column]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class PermissionHandler
|
102
|
+
def initialize(permissions, node)
|
103
|
+
a = node.attributes
|
104
|
+
|
105
|
+
@action = PermissionGroup.new(node.name, a['to'] || a['from']).tap do |pg|
|
106
|
+
permissions << pg
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def handle_permission_element(parse)
|
111
|
+
a = parse.node.attributes
|
112
|
+
@action.permissions << Permission.new(a)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mkxms/mssql/keylike_constraint_helper'
|
2
|
+
|
3
|
+
module Mkxms::Mssql
|
4
|
+
class PrimaryKey < Mkxms::Mssql::KeylikeConstraint
|
5
|
+
SQL_CONSTRAINT_TYPE = 'PRIMARY KEY'
|
6
|
+
|
7
|
+
def sql_constraint_type
|
8
|
+
SQL_CONSTRAINT_TYPE
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class PrimaryKeyHandler
|
13
|
+
include PropertyHandler::ElementHandler
|
14
|
+
|
15
|
+
def initialize(constraints, node)
|
16
|
+
a = node.attributes
|
17
|
+
|
18
|
+
@pkey = PrimaryKey.new(a).tap do |c|
|
19
|
+
constraints << c
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def extended_properties
|
24
|
+
@pkey.extended_properties
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_column_element(parse)
|
28
|
+
a = parse.node.attributes
|
29
|
+
|
30
|
+
raise UnsupportedFeatureError.new("Primary keys may not specify included columns (#{@pkey.qualified_table})") if a['included']
|
31
|
+
@pkey.columns << IndexColumn.new(a['name'], a['desc'] ? :descending : :ascending)
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: Handle partitioned primary keys
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'mkxms/mssql/utils'
|
3
|
+
|
4
|
+
module Mkxms; end
|
5
|
+
|
6
|
+
module Mkxms::Mssql
|
7
|
+
module ExtendedProperties
|
8
|
+
def extended_properties
|
9
|
+
@extended_properties ||= {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Property
|
14
|
+
def self.addition_sql(name, value, subject_identification_parts)
|
15
|
+
"EXEC sp_addextendedproperty N'%s', %s, %s;" % [
|
16
|
+
name,
|
17
|
+
value,
|
18
|
+
subject_identification_parts.map {|part| "N'#{part}'"}.join(', ')
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
module Hosting
|
23
|
+
def extended_properties_sql
|
24
|
+
self.extended_properties.each_pair.map do |name, value|
|
25
|
+
Mkxms::Mssql::Property.addition_sql(name, value, self.property_subject_identifiers)
|
26
|
+
end.tap do |v|
|
27
|
+
class <<v
|
28
|
+
def joined_on_new_lines(indent: ' ')
|
29
|
+
map {|i| "\n" + indent + i}.join('')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module SchemaScoped
|
37
|
+
def property_subject_identifiers
|
38
|
+
['SCHEMA', Utils::unquoted_name(schema), self.class::SQL_OBJECT_TYPE.upcase, Utils.unquoted_name(name)]
|
39
|
+
end
|
40
|
+
|
41
|
+
def subitem_extended_properties_sql(subitem)
|
42
|
+
subitem.extended_properties.each_pair.map do |name, value|
|
43
|
+
Mkxms::Mssql::Property.addition_sql(
|
44
|
+
name, value,
|
45
|
+
property_subject_identifiers + [subitem.class::SQL_OBJECT_TYPE.upcase, Utils.unquoted_name(subitem.name)]
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class PropertyHandler
|
53
|
+
module ElementHandler
|
54
|
+
def handle_property_element(parse)
|
55
|
+
parse.context = PropertyHandler.new(self, parse.node.attributes)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize(describable, attrs)
|
60
|
+
@describable = describable
|
61
|
+
@name = attrs['name']
|
62
|
+
@value_type = attrs['type'].downcase
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_text(property_value, node)
|
66
|
+
stored_value = property_value.dup
|
67
|
+
|
68
|
+
stored_value = Base64.decode64(stored_value) if @value_type.include? 'binary'
|
69
|
+
|
70
|
+
stored_value.define_singleton_method(
|
71
|
+
:to_sql_literal,
|
72
|
+
&(case @value_type
|
73
|
+
when 'char', 'varchar', 'uniqueidentifier', 'smalldatetime', 'datetime'
|
74
|
+
->() {"'#{self}'"}
|
75
|
+
when 'nchar', 'nvarchar'
|
76
|
+
->() {"N'#{self}'"}
|
77
|
+
when 'binary', 'varbinary'
|
78
|
+
->() {"0x" + self.bytes.map {|b| "%02x" % b}.join}
|
79
|
+
else
|
80
|
+
->() {self.to_s}
|
81
|
+
end)
|
82
|
+
)
|
83
|
+
|
84
|
+
@describable.extended_properties[@name] = stored_value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Mkxms; end
|
2
|
+
|
3
|
+
module Mkxms::Mssql
|
4
|
+
class QueryCursor
|
5
|
+
def initialize(select_statement, variables, options = {})
|
6
|
+
@select_statement = select_statement
|
7
|
+
@select_statement += ';' unless @select_statement.end_with? ';'
|
8
|
+
@cursor = options[:cursor_name] || self.class.generated_cursor_name
|
9
|
+
@out = options[:output_to] || $stdout
|
10
|
+
@global = options[:global]
|
11
|
+
@indent = options[:indent] || ' '
|
12
|
+
|
13
|
+
@variable_decl = variables.gsub(/\s+/, ' ')
|
14
|
+
@variable_names = variables.split(',').map do |vardecl|
|
15
|
+
vardecl.chomp.split(nil, 2)[0]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :cursor_name
|
20
|
+
|
21
|
+
def each_row
|
22
|
+
set_up_loop
|
23
|
+
fetch_next
|
24
|
+
@out.puts "WHILE @@FETCH_STATUS = 0"
|
25
|
+
@out.puts "BEGIN"
|
26
|
+
yield
|
27
|
+
fetch_next(@indent)
|
28
|
+
@out.puts "END;"
|
29
|
+
end
|
30
|
+
|
31
|
+
class ExpectedRowTest
|
32
|
+
def initialize(test_proc)
|
33
|
+
@test_proc = test_proc
|
34
|
+
end
|
35
|
+
|
36
|
+
def row(*args, &blk)
|
37
|
+
@test_proc.call(*args, &blk)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def expectations(opts = {})
|
42
|
+
extra_action = expectation_failure_action(opts[:on_extra])
|
43
|
+
test_entry_proc = if missing_action = expectation_failure_action(opts[:on_missing])
|
44
|
+
proc {|&blk| test_entry(on_missing: missing_action, &blk)}
|
45
|
+
else
|
46
|
+
method(:test_entry)
|
47
|
+
end
|
48
|
+
|
49
|
+
set_up_loop
|
50
|
+
|
51
|
+
yield ExpectedRowTest.new(test_entry_proc)
|
52
|
+
|
53
|
+
if extra_action
|
54
|
+
fetch_next
|
55
|
+
@out.puts "IF @@FETCH_STATUS = 0"
|
56
|
+
@out.puts "BEGIN"
|
57
|
+
extra_action.call
|
58
|
+
@out.puts "END;"
|
59
|
+
end
|
60
|
+
|
61
|
+
tear_down_loop
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_entry(opts = {})
|
65
|
+
opts = {} unless opts.kind_of? Hash
|
66
|
+
missing_action = expectation_failure_action(opts[:on_missing]) || proc {}
|
67
|
+
fetch_next
|
68
|
+
@out.puts "IF @@FETCH_STATUS <> 0"
|
69
|
+
@out.puts "BEGIN"
|
70
|
+
missing_action.call
|
71
|
+
@out.puts "END ELSE BEGIN"
|
72
|
+
yield
|
73
|
+
@out.puts "END;"
|
74
|
+
end
|
75
|
+
|
76
|
+
def expectation_failure_action(value)
|
77
|
+
case value
|
78
|
+
when Proc then value
|
79
|
+
when String then proc {@out.puts(@indent + value)}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def cursor_scope(explicit_local = true)
|
84
|
+
case
|
85
|
+
when @global then 'GLOBAL'
|
86
|
+
when explicit_local then 'LOCAL'
|
87
|
+
else ''
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def set_up_loop
|
92
|
+
@out.puts "DECLARE #@variable_decl;"
|
93
|
+
@out.puts "DECLARE #@cursor CURSOR #{cursor_scope} FOR"
|
94
|
+
@out.puts @select_statement
|
95
|
+
@out.puts "OPEN #@cursor;"
|
96
|
+
end
|
97
|
+
|
98
|
+
def fetch_next(indent = '')
|
99
|
+
@out.puts(indent + "FETCH NEXT FROM #@cursor INTO #{@variable_names.join(', ')};")
|
100
|
+
end
|
101
|
+
|
102
|
+
def tear_down_loop
|
103
|
+
@out.puts "CLOSE #{cursor_scope(false)} #@cursor; DEALLOCATE #{cursor_scope(false)} #@cursor;"
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.generated_cursor_name
|
107
|
+
@gensym_number ||= 0
|
108
|
+
"gensym_cursor_#{@gensym_number += 1}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|