@@ -62,27 +62,44 @@ def indexes(table_name, _name = nil)
62
62
end
63
63
end
64
64
65
+ def retrieve_column_data ( table_name )
66
+ column_query = "SHOW COLUMNS IN TABLE #{ table_name } "
67
+
68
+ # Temporarily disable debug logging so we don't spam the log with table column queries
69
+ query_results = ActiveRecord ::Base . logger . silence do
70
+ exec_query ( column_query )
71
+ end
72
+
73
+ column_data = query_results . map do |query_result |
74
+ data_type_parsed = JSON . parse ( query_result [ "data_type" ] )
75
+ {
76
+ column_name : query_result [ "column_name" ] ,
77
+ col_default : extract_default_from_snowflake ( query_result [ "default" ] ) ,
78
+ col_native_type : extract_data_type_from_snowflake ( data_type_parsed [ "type" ] ) ,
79
+ column_size : extract_column_size_from_snowflake ( data_type_parsed ) ,
80
+ numeric_scale : extract_scale_from_snowflake ( data_type_parsed ) ,
81
+ is_nullable : data_type_parsed [ "nullable" ]
82
+ }
83
+ end
84
+
85
+ column_data
86
+ end
87
+
88
+
65
89
# Returns an array of Column objects for the table specified by
66
90
# +table_name+.
91
+ # This entire function has been customized for Snowflake and will not work in general.
67
92
def columns ( table_name , _name = nil )
68
- stmt = @connection . columns ( native_case ( table_name . to_s ) )
69
- result = stmt . fetch_all || [ ]
70
- stmt . drop
93
+ result = retrieve_column_data ( table_name )
71
94
72
- db_regex = name_regex ( current_database )
73
- schema_regex = name_regex ( current_schema )
74
95
result . each_with_object ( [ ] ) do |col , cols |
75
- next unless col [ 0 ] =~ db_regex && col [ 1 ] =~ schema_regex
76
- col_name = col [ 3 ] # SQLColumns: COLUMN_NAME
77
- col_default = col [ 12 ] # SQLColumns: COLUMN_DEF
78
- col_native_type = col [ 5 ] # SQLColumns: TYPE_NAME
79
- col_limit = col [ 6 ] # SQLColumns: COLUMN_SIZE
80
- col_scale = col [ 8 ] # SQLColumns: DECIMAL_DIGITS
96
+ col_name = col [ :column_name ]
97
+ col_default = col [ :col_default ]
98
+ col_native_type = col [ :col_native_type ]
99
+ col_limit = col [ :column_size ]
100
+ col_scale = col [ :numeric_scale ]
101
+ col_nullable = col [ :is_nullable ]
81
102
82
- # SQLColumns: IS_NULLABLE, SQLColumns: NULLABLE
83
- col_nullable = nullability ( col_name , col [ 17 ] , col [ 10 ] )
84
-
85
- # This section has been customized for Snowflake and will not work in general.
86
103
args = { sql_type : construct_sql_type ( col_native_type , col_limit , col_scale ) , type : col_native_type , limit : col_limit }
87
104
args [ :type ] = case col_native_type
88
105
when "BOOLEAN" then :boolean
@@ -109,13 +126,6 @@ def columns(table_name, _name = nil)
109
126
110
127
sql_type_metadata = ActiveRecord ::ConnectionAdapters ::SqlTypeMetadata . new ( **args )
111
128
112
- # The @connection.columns function returns empty strings for column defaults.
113
- # Even when the column has a default value. This is a call to the ODBC layer
114
- # with only enough Ruby to make the call happen. Replacing the empty string
115
- # with nil permits Rails to set the current datetime for created_at and
116
- # updated_at on model creates and updates.
117
- col_default = nil if col_default == ""
118
-
119
129
cols << new_column ( format_case ( col_name ) , col_default , sql_type_metadata , col_nullable , col_native_type )
120
130
end
121
131
end
@@ -189,5 +199,69 @@ def construct_sql_type(native_type, limit, scale)
189
199
native_type
190
200
end
191
201
end
202
+
203
+ private
204
+
205
+ # Extracts the value from a Snowflake column default definition.
206
+ def extract_default_from_snowflake ( default )
207
+ case default
208
+ # null
209
+ when nil
210
+ nil
211
+ # Quoted strings
212
+ when /\A [(B]?'(.*)'\z /m
213
+ $1. gsub ( "''" , "'" ) . gsub ( "\\ \\ " , "\\ " )
214
+ # Boolean types
215
+ when "TRUE"
216
+ "true"
217
+ when "FALSE"
218
+ "false"
219
+ # Numeric types
220
+ when /\A (-?\d +(\. \d *)?)\z /
221
+ $1
222
+ else
223
+ nil
224
+ end
225
+ end
226
+
227
+ def extract_data_type_from_snowflake ( snowflake_data_type )
228
+ case snowflake_data_type
229
+ when "NUMBER"
230
+ "DECIMAL"
231
+ when /\A TIMESTAMP_.*/
232
+ "TIMESTAMP"
233
+ when "TEXT"
234
+ "VARCHAR"
235
+ when "FLOAT"
236
+ "DOUBLE"
237
+ when "FIXED"
238
+ "DECIMAL"
239
+ when "REAL"
240
+ "DOUBLE"
241
+ else
242
+ snowflake_data_type
243
+ end
244
+ end
245
+
246
+ def extract_column_size_from_snowflake ( type_information )
247
+ case type_information [ "type" ]
248
+ when /\A TIMESTAMP_.*/
249
+ 35
250
+ when "DATE"
251
+ 10
252
+ when "FLOAT"
253
+ 38
254
+ when "REAL"
255
+ 38
256
+ when "BOOLEAN"
257
+ 1
258
+ else
259
+ type_information [ "length" ] || type_information [ "precision" ] || 0
260
+ end
261
+ end
262
+
263
+ def extract_scale_from_snowflake ( type_information )
264
+ type_information [ "scale" ] || 0
265
+ end
192
266
end
193
267
end
0 commit comments