Skip to content

Commit 8ad3e62

Browse files
Moj 519 plan years controller (localytics#27)
* MOJ-519 Rewrote the column retrieval to use the information schema instead of odbc calls so we can get default values * MOJ-519 Removed unnecessary check for empty string default value * MOJ-519 Removed the idea of the table store. May be worth revisiting later on, but with multiple instances of the connection I'm not confident that the small gains are worthwhile. * MOJ-519 Committing code with debug still in place * MOJ-519 Removed the debug code and made the private methods private * MOJ-519 Remove case changes from the columns query so it can be used with quoted table names
1 parent 6be7bb6 commit 8ad3e62

File tree

1 file changed

+96
-22
lines changed

1 file changed

+96
-22
lines changed

lib/odbc_adapter/schema_statements.rb

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,27 +62,44 @@ def indexes(table_name, _name = nil)
6262
end
6363
end
6464

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+
6589
# Returns an array of Column objects for the table specified by
6690
# +table_name+.
91+
# This entire function has been customized for Snowflake and will not work in general.
6792
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)
7194

72-
db_regex = name_regex(current_database)
73-
schema_regex = name_regex(current_schema)
7495
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]
81102

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.
86103
args = { sql_type: construct_sql_type(col_native_type, col_limit, col_scale), type: col_native_type, limit: col_limit }
87104
args[:type] = case col_native_type
88105
when "BOOLEAN" then :boolean
@@ -109,13 +126,6 @@ def columns(table_name, _name = nil)
109126

110127
sql_type_metadata = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new(**args)
111128

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-
119129
cols << new_column(format_case(col_name), col_default, sql_type_metadata, col_nullable, col_native_type)
120130
end
121131
end
@@ -189,5 +199,69 @@ def construct_sql_type(native_type, limit, scale)
189199
native_type
190200
end
191201
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 /\ATIMESTAMP_.*/
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 /\ATIMESTAMP_.*/
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
192266
end
193267
end

0 commit comments

Comments
 (0)