mkxms-mssql 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|