database-model-generator 0.6.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
- checksums.yaml.gz.sig +3 -0
- data/CHANGES.md +59 -0
- data/Gemfile +2 -0
- data/LICENSE +177 -0
- data/MANIFEST.md +10 -0
- data/README.md +186 -0
- data/Rakefile +32 -0
- data/bin/dmg +831 -0
- data/certs/djberg96_pub.pem +26 -0
- data/database-model-generator.gemspec +31 -0
- data/docker/README.md +238 -0
- data/docker/oracle/Dockerfile +87 -0
- data/docker/oracle/README.md +140 -0
- data/docker/oracle/docker-compose.yml +41 -0
- data/docker/oracle/test.sh +45 -0
- data/docker/sqlserver/DOCKER.md +152 -0
- data/docker/sqlserver/Dockerfile +29 -0
- data/docker/sqlserver/SUPPORT.md +477 -0
- data/docker/sqlserver/TESTING.md +194 -0
- data/docker/sqlserver/docker-compose.yml +52 -0
- data/docker/sqlserver/init-db.sql +158 -0
- data/docker/sqlserver/run_tests.sh +154 -0
- data/docker/sqlserver/setup-db.sh +9 -0
- data/docker/sqlserver/test-Dockerfile +36 -0
- data/docker/sqlserver/test.sh +56 -0
- data/lib/database_model_generator.rb +652 -0
- data/lib/oracle/model/generator.rb +287 -0
- data/lib/sqlserver/model/generator.rb +281 -0
- data/spec/oracle_model_generator_spec.rb +176 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/oracle_connection.rb +126 -0
- data.tar.gz.sig +0 -0
- metadata +162 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
require_relative '../../database_model_generator'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'oci8'
|
5
|
+
rescue LoadError
|
6
|
+
# OCI8 not available - Oracle support will be disabled
|
7
|
+
end
|
8
|
+
|
9
|
+
module Oracle
|
10
|
+
module Model
|
11
|
+
class Generator < DatabaseModel::Generator::Base
|
12
|
+
VERSION = DatabaseModel::Generator::VERSION
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate_connection(connection)
|
17
|
+
unless defined?(OCI8) && connection.is_a?(OCI8)
|
18
|
+
raise ArgumentError, "Connection must be an OCI8 object. Install 'oci8' gem for Oracle support."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def normalize_table_name(table)
|
23
|
+
table.upcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_table_exists(table)
|
27
|
+
cursor = nil
|
28
|
+
begin
|
29
|
+
sql = "SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME = ?"
|
30
|
+
cursor = @connection.parse(sql)
|
31
|
+
cursor.bind_param(1, table.upcase)
|
32
|
+
cursor.exec
|
33
|
+
result = cursor.fetch
|
34
|
+
result && result[0] > 0
|
35
|
+
rescue => e
|
36
|
+
puts "Error checking table existence: #{e.message}"
|
37
|
+
false
|
38
|
+
ensure
|
39
|
+
cursor.close if cursor
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
<<<<<<< Updated upstream
|
44
|
+
# Public method to get readable constraint information
|
45
|
+
def constraint_summary
|
46
|
+
return {} unless generated?
|
47
|
+
|
48
|
+
summary = Hash.new { |h, k| h[k] = [] }
|
49
|
+
@constraints.each do |constraint|
|
50
|
+
type = case constraint['CONSTRAINT_TYPE']
|
51
|
+
when 'P' then 'Primary Key'
|
52
|
+
when 'R' then 'Foreign Key'
|
53
|
+
when 'U' then 'Unique'
|
54
|
+
when 'C' then 'Check'
|
55
|
+
else constraint['CONSTRAINT_TYPE']
|
56
|
+
end
|
57
|
+
summary[constraint['COLUMN_NAME'].downcase] << type
|
58
|
+
end
|
59
|
+
summary
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Reset internal state - useful for error recovery
|
65
|
+
def reset_state
|
66
|
+
@constraints.clear
|
67
|
+
@primary_keys.clear
|
68
|
+
@foreign_keys.clear
|
69
|
+
@dependencies.clear
|
70
|
+
@belongs_to.clear
|
71
|
+
@column_info.clear
|
72
|
+
@table = nil
|
73
|
+
@model = nil
|
74
|
+
@view = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# Generate a more intelligent model name from table name
|
78
|
+
def generate_model_name(table)
|
79
|
+
# Handle common table naming patterns
|
80
|
+
name = table.split('_').map{ |part| part.downcase.capitalize }.join
|
81
|
+
|
82
|
+
# Remove trailing 's' for pluralized table names
|
83
|
+
name.chop! if name.length > 1 && name[-1].chr.upcase == 'S'
|
84
|
+
|
85
|
+
# Handle special cases
|
86
|
+
case name.downcase
|
87
|
+
when 'people'
|
88
|
+
'Person'
|
89
|
+
when 'children'
|
90
|
+
'Child'
|
91
|
+
when 'data'
|
92
|
+
'Datum'
|
93
|
+
else
|
94
|
+
name
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
=======
|
99
|
+
>>>>>>> Stashed changes
|
100
|
+
def get_column_info
|
101
|
+
cursor = nil
|
102
|
+
begin
|
103
|
+
if @view
|
104
|
+
sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE
|
105
|
+
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? ORDER BY COLUMN_ID"
|
106
|
+
else
|
107
|
+
sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE
|
108
|
+
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = ? ORDER BY COLUMN_ID"
|
109
|
+
end
|
110
|
+
|
111
|
+
cursor = @connection.parse(sql)
|
112
|
+
cursor.bind_param(1, @table)
|
113
|
+
cursor.exec
|
114
|
+
|
115
|
+
@column_info = []
|
116
|
+
while row = cursor.fetch_hash
|
117
|
+
@column_info << OracleColumnInfo.new(row)
|
118
|
+
end
|
119
|
+
rescue => e
|
120
|
+
raise "Error retrieving column information: #{e.message}"
|
121
|
+
ensure
|
122
|
+
cursor.close if cursor
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_primary_keys
|
127
|
+
cursor = nil
|
128
|
+
begin
|
129
|
+
sql = "SELECT column_name FROM user_cons_columns
|
130
|
+
WHERE constraint_name = (
|
131
|
+
SELECT constraint_name FROM user_constraints
|
132
|
+
WHERE table_name = ? AND constraint_type = 'P'
|
133
|
+
) ORDER BY position"
|
134
|
+
|
135
|
+
cursor = @connection.parse(sql)
|
136
|
+
cursor.bind_param(1, @table)
|
137
|
+
cursor.exec
|
138
|
+
|
139
|
+
@primary_keys = []
|
140
|
+
while row = cursor.fetch
|
141
|
+
@primary_keys << row[0]
|
142
|
+
end
|
143
|
+
rescue => e
|
144
|
+
raise "Error retrieving primary keys: #{e.message}"
|
145
|
+
ensure
|
146
|
+
cursor.close if cursor
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_foreign_keys
|
151
|
+
cursor = nil
|
152
|
+
begin
|
153
|
+
sql = "SELECT column_name FROM user_cons_columns
|
154
|
+
WHERE constraint_name IN (
|
155
|
+
SELECT constraint_name FROM user_constraints
|
156
|
+
WHERE table_name = ? AND constraint_type = 'R'
|
157
|
+
)"
|
158
|
+
|
159
|
+
cursor = @connection.parse(sql)
|
160
|
+
cursor.bind_param(1, @table)
|
161
|
+
cursor.exec
|
162
|
+
|
163
|
+
@foreign_keys = []
|
164
|
+
while row = cursor.fetch
|
165
|
+
@foreign_keys << row[0]
|
166
|
+
end
|
167
|
+
rescue => e
|
168
|
+
raise "Error retrieving foreign keys: #{e.message}"
|
169
|
+
ensure
|
170
|
+
cursor.close if cursor
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_constraints
|
175
|
+
cursor = nil
|
176
|
+
begin
|
177
|
+
sql = "SELECT c.constraint_name, c.constraint_type, cc.column_name, c.search_condition
|
178
|
+
FROM user_constraints c, user_cons_columns cc
|
179
|
+
WHERE c.table_name = ? AND c.constraint_name = cc.constraint_name"
|
180
|
+
|
181
|
+
cursor = @connection.parse(sql)
|
182
|
+
cursor.bind_param(1, @table)
|
183
|
+
cursor.exec
|
184
|
+
|
185
|
+
@constraints = []
|
186
|
+
while row = cursor.fetch_hash
|
187
|
+
@constraints << row
|
188
|
+
end
|
189
|
+
rescue => e
|
190
|
+
raise "Error retrieving constraints: #{e.message}"
|
191
|
+
ensure
|
192
|
+
cursor.close if cursor
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def get_dependencies
|
197
|
+
cursor = nil
|
198
|
+
begin
|
199
|
+
sql = "SELECT REFERENCED_NAME as NAME, REFERENCED_TYPE as TYPE
|
200
|
+
FROM USER_DEPENDENCIES WHERE NAME = ? AND TYPE = 'TABLE'"
|
201
|
+
|
202
|
+
cursor = @connection.parse(sql)
|
203
|
+
cursor.bind_param(1, @table)
|
204
|
+
cursor.exec
|
205
|
+
|
206
|
+
@dependencies = []
|
207
|
+
while row = cursor.fetch_hash
|
208
|
+
@dependencies << row
|
209
|
+
end
|
210
|
+
rescue => e
|
211
|
+
raise "Error retrieving dependencies: #{e.message}"
|
212
|
+
ensure
|
213
|
+
cursor.close if cursor
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def format_constraint_type(constraint)
|
218
|
+
case constraint['CONSTRAINT_TYPE']
|
219
|
+
when 'P' then 'Primary Key'
|
220
|
+
when 'R' then 'Foreign Key'
|
221
|
+
when 'U' then 'Unique'
|
222
|
+
when 'C' then 'Check'
|
223
|
+
else constraint['CONSTRAINT_TYPE']
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def get_constraint_column_name(constraint)
|
228
|
+
constraint['COLUMN_NAME']
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_constraint_text(constraint)
|
232
|
+
constraint['SEARCH_CONDITION']
|
233
|
+
end
|
234
|
+
|
235
|
+
def is_string_type?(column)
|
236
|
+
column.data_type.to_s.downcase =~ /(char|varchar|varchar2|clob)/
|
237
|
+
end
|
238
|
+
|
239
|
+
def is_date_type?(column)
|
240
|
+
column.data_type.to_s.downcase =~ /(date|timestamp)/
|
241
|
+
end
|
242
|
+
|
243
|
+
def is_text_type?(column)
|
244
|
+
column.data_type.to_s.downcase =~ /(varchar|char|clob)/
|
245
|
+
end
|
246
|
+
|
247
|
+
def get_column_size(column)
|
248
|
+
column.data_size
|
249
|
+
end
|
250
|
+
|
251
|
+
def build_full_text_index_sql(column)
|
252
|
+
"CREATE INDEX idx_#{@table.downcase}_#{column.name.downcase}_text ON #{@table.upcase} (#{column.name.upcase}) INDEXTYPE IS CTXSYS.CONTEXT"
|
253
|
+
end
|
254
|
+
|
255
|
+
def get_full_text_index_type
|
256
|
+
"Oracle Text Index"
|
257
|
+
end
|
258
|
+
|
259
|
+
def find_fk_table(fk)
|
260
|
+
# Oracle-specific logic for finding referenced table
|
261
|
+
fk.gsub(/_id$/i, '').pluralize rescue "#{fk.gsub(/_id$/i, '')}s"
|
262
|
+
end
|
263
|
+
|
264
|
+
def disconnect
|
265
|
+
@connection.logoff if @connection
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Oracle column info wrapper
|
270
|
+
class OracleColumnInfo
|
271
|
+
attr_reader :name, :data_type, :data_size, :precision, :scale, :nullable
|
272
|
+
|
273
|
+
def initialize(row)
|
274
|
+
@name = row['COLUMN_NAME']
|
275
|
+
@data_type = row['DATA_TYPE']
|
276
|
+
@data_size = row['DATA_LENGTH']
|
277
|
+
@precision = row['DATA_PRECISION']
|
278
|
+
@scale = row['DATA_SCALE']
|
279
|
+
@nullable = row['NULLABLE'] == 'Y'
|
280
|
+
end
|
281
|
+
|
282
|
+
def nullable?
|
283
|
+
@nullable
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require_relative '../../database_model_generator'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'tiny_tds'
|
5
|
+
rescue LoadError
|
6
|
+
# TinyTds not available - SQL Server support will be disabled
|
7
|
+
end
|
8
|
+
|
9
|
+
module SqlServer
|
10
|
+
module Model
|
11
|
+
class Generator < DatabaseModel::Generator::Base
|
12
|
+
VERSION = DatabaseModel::Generator::VERSION
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate_connection(connection)
|
17
|
+
unless defined?(TinyTds) && connection.is_a?(TinyTds::Client)
|
18
|
+
raise ArgumentError, "Connection must be a TinyTds::Client object. Install 'tiny_tds' gem for SQL Server support."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def normalize_table_name(table)
|
23
|
+
table # SQL Server table names are case-sensitive, preserve as-is
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_table_exists(table)
|
27
|
+
begin
|
28
|
+
sql = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '#{table}'"
|
29
|
+
result = @connection.execute(sql)
|
30
|
+
row = result.first
|
31
|
+
row && row.values.first > 0
|
32
|
+
rescue => e
|
33
|
+
puts "Error checking table existence: #{e.message}"
|
34
|
+
false
|
35
|
+
ensure
|
36
|
+
result.cancel if result && result.respond_to?(:cancel)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_column_info
|
41
|
+
begin
|
42
|
+
sql = <<~SQL
|
43
|
+
SELECT
|
44
|
+
COLUMN_NAME,
|
45
|
+
DATA_TYPE,
|
46
|
+
CHARACTER_MAXIMUM_LENGTH,
|
47
|
+
NUMERIC_PRECISION,
|
48
|
+
NUMERIC_SCALE,
|
49
|
+
IS_NULLABLE,
|
50
|
+
COLUMN_DEFAULT
|
51
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
52
|
+
WHERE TABLE_NAME = '#{@table}'
|
53
|
+
ORDER BY ORDINAL_POSITION
|
54
|
+
SQL
|
55
|
+
|
56
|
+
result = @connection.execute(sql)
|
57
|
+
@column_info = []
|
58
|
+
|
59
|
+
result.each do |row|
|
60
|
+
@column_info << SqlServerColumnInfo.new(row)
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
raise "Error retrieving column information: #{e.message}"
|
64
|
+
ensure
|
65
|
+
result.cancel if result && result.respond_to?(:cancel)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_primary_keys
|
70
|
+
begin
|
71
|
+
sql = <<~SQL
|
72
|
+
SELECT c.COLUMN_NAME
|
73
|
+
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
74
|
+
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
|
75
|
+
JOIN INFORMATION_SCHEMA.COLUMNS c ON ccu.COLUMN_NAME = c.COLUMN_NAME AND ccu.TABLE_NAME = c.TABLE_NAME
|
76
|
+
WHERE tc.TABLE_NAME = '#{@table}' AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
77
|
+
ORDER BY c.ORDINAL_POSITION
|
78
|
+
SQL
|
79
|
+
|
80
|
+
result = @connection.execute(sql)
|
81
|
+
@primary_keys = []
|
82
|
+
|
83
|
+
result.each do |row|
|
84
|
+
@primary_keys << row['COLUMN_NAME']
|
85
|
+
end
|
86
|
+
rescue => e
|
87
|
+
raise "Error retrieving primary keys: #{e.message}"
|
88
|
+
ensure
|
89
|
+
result.cancel if result && result.respond_to?(:cancel)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_foreign_keys
|
94
|
+
begin
|
95
|
+
sql = <<~SQL
|
96
|
+
SELECT ccu.COLUMN_NAME
|
97
|
+
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
98
|
+
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
|
99
|
+
WHERE tc.TABLE_NAME = '#{@table}' AND tc.CONSTRAINT_TYPE = 'FOREIGN KEY'
|
100
|
+
SQL
|
101
|
+
|
102
|
+
result = @connection.execute(sql)
|
103
|
+
@foreign_keys = []
|
104
|
+
|
105
|
+
result.each do |row|
|
106
|
+
@foreign_keys << row['COLUMN_NAME']
|
107
|
+
end
|
108
|
+
rescue => e
|
109
|
+
raise "Error retrieving foreign keys: #{e.message}"
|
110
|
+
ensure
|
111
|
+
result.cancel if result && result.respond_to?(:cancel)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_constraints
|
116
|
+
begin
|
117
|
+
sql = <<~SQL
|
118
|
+
SELECT
|
119
|
+
tc.CONSTRAINT_NAME,
|
120
|
+
tc.CONSTRAINT_TYPE,
|
121
|
+
ccu.COLUMN_NAME,
|
122
|
+
cc.CHECK_CLAUSE
|
123
|
+
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
124
|
+
LEFT JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME
|
125
|
+
LEFT JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
|
126
|
+
WHERE tc.TABLE_NAME = '#{@table}'
|
127
|
+
SQL
|
128
|
+
|
129
|
+
result = @connection.execute(sql)
|
130
|
+
@constraints = []
|
131
|
+
|
132
|
+
result.each do |row|
|
133
|
+
@constraints << {
|
134
|
+
'CONSTRAINT_NAME' => row['CONSTRAINT_NAME'],
|
135
|
+
'CONSTRAINT_TYPE' => row['CONSTRAINT_TYPE'],
|
136
|
+
'COLUMN_NAME' => row['COLUMN_NAME'],
|
137
|
+
'CHECK_CLAUSE' => row['CHECK_CLAUSE']
|
138
|
+
}
|
139
|
+
end
|
140
|
+
rescue => e
|
141
|
+
raise "Error retrieving constraints: #{e.message}"
|
142
|
+
ensure
|
143
|
+
result.cancel if result && result.respond_to?(:cancel)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_dependencies
|
148
|
+
begin
|
149
|
+
sql = <<~SQL
|
150
|
+
SELECT
|
151
|
+
OBJECT_NAME(referencing_id) AS NAME,
|
152
|
+
'TABLE' AS TYPE
|
153
|
+
FROM sys.sql_expression_dependencies sed
|
154
|
+
JOIN sys.objects o ON sed.referenced_id = o.object_id
|
155
|
+
WHERE o.name = '#{@table}' AND o.type = 'U'
|
156
|
+
SQL
|
157
|
+
|
158
|
+
result = @connection.execute(sql)
|
159
|
+
@dependencies = []
|
160
|
+
|
161
|
+
result.each do |row|
|
162
|
+
@dependencies << {
|
163
|
+
'NAME' => row['NAME'],
|
164
|
+
'TYPE' => row['TYPE']
|
165
|
+
}
|
166
|
+
end
|
167
|
+
rescue => e
|
168
|
+
# Dependencies query may fail on some SQL Server versions, continue without error
|
169
|
+
@dependencies = []
|
170
|
+
ensure
|
171
|
+
result.cancel if result && result.respond_to?(:cancel)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def format_constraint_type(constraint)
|
176
|
+
case constraint['CONSTRAINT_TYPE']
|
177
|
+
when 'PRIMARY KEY' then 'Primary Key'
|
178
|
+
when 'FOREIGN KEY' then 'Foreign Key'
|
179
|
+
when 'UNIQUE' then 'Unique'
|
180
|
+
when 'CHECK' then 'Check'
|
181
|
+
else constraint['CONSTRAINT_TYPE']
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_constraint_column_name(constraint)
|
186
|
+
constraint['COLUMN_NAME']
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_constraint_text(constraint)
|
190
|
+
constraint['CHECK_CLAUSE']
|
191
|
+
end
|
192
|
+
|
193
|
+
def is_string_type?(column)
|
194
|
+
column.data_type.downcase =~ /(char|varchar|nchar|nvarchar|text|ntext)/
|
195
|
+
end
|
196
|
+
|
197
|
+
def is_date_type?(column)
|
198
|
+
column.data_type.downcase =~ /(date|time|datetime|datetime2|smalldatetime|datetimeoffset)/
|
199
|
+
end
|
200
|
+
|
201
|
+
def is_text_type?(column)
|
202
|
+
column.data_type.downcase =~ /(varchar|nvarchar|char|nchar|text|ntext)/
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_column_size(column)
|
206
|
+
column.character_maximum_length
|
207
|
+
end
|
208
|
+
|
209
|
+
def build_full_text_index_sql(column)
|
210
|
+
"CREATE FULLTEXT INDEX ON #{@table} (#{column.name.upcase}) KEY INDEX PK_#{@table}"
|
211
|
+
end
|
212
|
+
|
213
|
+
def get_full_text_index_type
|
214
|
+
"SQL Server Full-Text Index"
|
215
|
+
end
|
216
|
+
|
217
|
+
def find_fk_table(fk)
|
218
|
+
# SQL Server-specific logic for finding referenced table
|
219
|
+
# Try to get actual referenced table from foreign key constraints
|
220
|
+
begin
|
221
|
+
sql = <<~SQL
|
222
|
+
SELECT
|
223
|
+
OBJECT_NAME(f.referenced_object_id) AS referenced_table
|
224
|
+
FROM sys.foreign_keys f
|
225
|
+
JOIN sys.foreign_key_columns fc ON f.object_id = fc.constraint_object_id
|
226
|
+
JOIN sys.columns c ON fc.parent_object_id = c.object_id AND fc.parent_column_id = c.column_id
|
227
|
+
WHERE f.parent_object_id = OBJECT_ID('#{@table}') AND c.name = '#{fk}'
|
228
|
+
SQL
|
229
|
+
|
230
|
+
result = @connection.execute(sql)
|
231
|
+
row = result.first
|
232
|
+
if row && row['referenced_table']
|
233
|
+
return row['referenced_table'].downcase
|
234
|
+
end
|
235
|
+
rescue => e
|
236
|
+
# Fall back to naming convention if query fails
|
237
|
+
ensure
|
238
|
+
result.cancel if result && result.respond_to?(:cancel)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Fallback to naming convention
|
242
|
+
fk.gsub(/_id$/i, '').pluralize rescue "#{fk.gsub(/_id$/i, '')}s"
|
243
|
+
end
|
244
|
+
|
245
|
+
def disconnect
|
246
|
+
@connection.close if @connection
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# SQL Server column info wrapper
|
251
|
+
class SqlServerColumnInfo
|
252
|
+
attr_reader :name, :data_type, :character_maximum_length, :numeric_precision, :numeric_scale, :nullable, :column_default
|
253
|
+
|
254
|
+
def initialize(row)
|
255
|
+
@name = row['COLUMN_NAME']
|
256
|
+
@data_type = row['DATA_TYPE']
|
257
|
+
@character_maximum_length = row['CHARACTER_MAXIMUM_LENGTH']
|
258
|
+
@numeric_precision = row['NUMERIC_PRECISION']
|
259
|
+
@numeric_scale = row['NUMERIC_SCALE']
|
260
|
+
@nullable = row['IS_NULLABLE'] == 'YES'
|
261
|
+
@column_default = row['COLUMN_DEFAULT']
|
262
|
+
end
|
263
|
+
|
264
|
+
def nullable?
|
265
|
+
@nullable
|
266
|
+
end
|
267
|
+
|
268
|
+
def data_size
|
269
|
+
@character_maximum_length
|
270
|
+
end
|
271
|
+
|
272
|
+
def precision
|
273
|
+
@numeric_precision
|
274
|
+
end
|
275
|
+
|
276
|
+
def scale
|
277
|
+
@numeric_scale
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|