Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions activerecord-sqlserver-adapter.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Gem::Specification.new do |spec|
spec.description = "ActiveRecord SQL Server Adapter. SQL Server 2012 and upward."

spec.metadata = {
"bug_tracker_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues",
"changelog_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v#{version}/CHANGELOG.md",
"source_code_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v#{version}",
"bug_tracker_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues",
"changelog_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/blob/v#{version}/CHANGELOG.md",
"source_code_uri" => "https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/tree/v#{version}",
}

spec.files = `git ls-files -z`.split("\x0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,14 @@ def with_identity_insert_enabled(table_name)

def use_database(database = nil)
return if sqlserver_azure?

name = SQLServer::Utils.extract_identifiers(database || @connection_options[:database]).quoted
do_execute "USE #{name}" unless name.blank?
end

def user_options
return {} if sqlserver_azure?

rows = select_rows("DBCC USEROPTIONS WITH NO_INFOMSGS", "SCHEMA")
rows = rows.first if rows.size == 2 && rows.last.empty?
rows.reduce(HashWithIndifferentAccess.new) do |values, row|
Expand Down Expand Up @@ -315,6 +317,7 @@ def sp_executesql_types_and_parameters(binds)

def sp_executesql_sql_type(attr)
return attr.type.sqlserver_type if attr.type.respond_to?(:sqlserver_type)

case value = attr.value_for_database
when Numeric
value > 2_147_483_647 ? "bigint".freeze : "int".freeze
Expand Down Expand Up @@ -376,8 +379,10 @@ def exclude_output_inserted_table_names?

def exclude_output_inserted_table_name?(table_name, sql)
return false unless exclude_output_inserted_table_names?

table_name ||= get_table_name(sql)
return false unless table_name

self.class.exclude_output_inserted_table_names[table_name]
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,3 @@ def create_database_edition_options(options = {})
end
end
end



2 changes: 1 addition & 1 deletion lib/active_record/connection_adapters/sqlserver/quoting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def quoted_date(value)
if value.acts_like?(:date)
Type::Date.new.serialize(value)
else value.acts_like?(:time)
Type::DateTime.new.serialize(value)
Type::DateTime.new.serialize(value)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ def explicit_primary_key_default?(column)

def schema_limit(column)
return if SQLSEVER_NO_LIMIT_TYPES.include?(column.sql_type)

super
end

def schema_collation(column)
return unless column.collation

column.collation if column.collation != @connection.collation
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def indexes(table_name)

def columns(table_name)
return [] if table_name.blank?

column_definitions(table_name).map do |ci|
sqlserver_options = ci.slice :ordinal_position, :is_primary, :is_identity, :table_name
sql_type_metadata = fetch_type_metadata ci[:type], sqlserver_options
Expand Down Expand Up @@ -131,6 +132,7 @@ def rename_table(table_name, new_name)

def remove_column(table_name, column_name, type = nil, options = {})
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array

remove_check_constraints(table_name, column_name)
remove_default_constraint(table_name, column_name)
remove_indexes(table_name, column_name)
Expand All @@ -144,9 +146,9 @@ def change_column(table_name, column_name, type, options = {})
without_constraints = options.key?(:default) || options.key?(:limit)
default = if !options.key?(:default) && column_object
column_object.default
else
options[:default]
end
else
options[:default]
end
if without_constraints || (column_object && column_object.type != type.to_sym)
remove_default_constraint(table_name, column_name)
indexes = indexes(table_name).select { |index| index.columns.include?(column_name.to_s) }
Expand All @@ -172,6 +174,7 @@ def change_column_default(table_name, column_name, default_or_changes)
clear_cache!
column = column_for(table_name, column_name)
return unless column

remove_default_constraint(table_name, column_name)
default = extract_new_default_value(default_or_changes)
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
Expand All @@ -188,6 +191,7 @@ def rename_column(table_name, column_name, new_column_name)

def rename_index(table_name, old_name, new_name)
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length

identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
end
Expand Down Expand Up @@ -249,10 +253,10 @@ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **)

def columns_for_distinct(columns, orders)
order_columns = orders.reject(&:blank?).map { |s|
s = s.to_sql unless s.is_a?(String)
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
s = s.to_sql unless s.is_a?(String)
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
.gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }

(order_columns << super).join(", ")
end
Expand Down Expand Up @@ -507,11 +511,11 @@ def remove_indexes(table_name, column_name)
def get_table_name(sql)
tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
Regexp.last_match[3] || Regexp.last_match[4]
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
Regexp.last_match[1]
else
nil
end
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
Regexp.last_match[1]
else
nil
end
SQLServer::Utils.extract_identifiers(tn).object
end

Expand Down Expand Up @@ -540,9 +544,9 @@ def view_information(table_name)
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
view_info[:VIEW_DEFINITION] = begin
select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
rescue
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
nil
rescue
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
nil
end
end
end
Expand All @@ -553,6 +557,7 @@ def view_information(table_name)
def views_real_column_name(table_name, column_name)
view_definition = view_information(table_name)[:VIEW_DEFINITION]
return column_name unless view_definition

match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
match_data ? match_data[1] : column_name
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def sqlserver?

def current_isolation_level
return unless sqlserver?

level = connection.user_options_isolation_level
# When READ_COMMITTED_SNAPSHOT is set to ON,
# user_options_isolation_level will be equal to 'read committed
Expand Down
2 changes: 2 additions & 0 deletions lib/active_record/connection_adapters/sqlserver/type/char.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def type
def serialize(value)
return if value.nil?
return value if value.is_a?(Data)

Data.new super, self
end

Expand All @@ -24,6 +25,7 @@ def sqlserver_type

def quoted(value)
return value.quoted_id if value.respond_to?(:quoted_id)

Utils.quote_string_single(value)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def sqlserver_type

def serialize(value)
return unless value.present?

date = super(value).to_s(:_sqlserver_dateformat)
Data.new date, self
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def quoted(value)
def cast_value(value)
value = super
return if value.blank?

value = value.change year: 2000, month: 01, day: 01
apply_seconds_precision(value)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ module TimeValueFractional

def apply_seconds_precision(value)
return value if !value.respond_to?(fractional_property) || value.send(fractional_property).zero?

value.change fractional_property => seconds_precision(value)
end

def seconds_precision(value)
return 0 if fractional_scale == 0

seconds = value.send(fractional_property).to_f / fractional_operator.to_f
seconds = ((seconds * (1 / fractional_precision)).round / (1 / fractional_precision)).round(fractional_scale)
(seconds * fractional_operator).round(0).to_i
end

def quote_fractional(value)
return 0 if fractional_scale == 0

frac_seconds = seconds_precision(value)
seconds = (frac_seconds.to_f / fractional_operator.to_f).round(fractional_scale)
seconds.to_d.to_s.split(".").last.to(fractional_scale - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def sqlserver_type

def serialize(value)
return unless value

Data.new super, self
end

Expand Down
1 change: 1 addition & 0 deletions lib/active_record/connection_adapters/sqlserver/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def hash
def parse_raw_name
@parts = []
return if raw_name.blank?

scanner = StringScanner.new(raw_name)
matched = scanner.exist?(QUOTED_CHECKER) ? scanner.scan_until(QUOTED_SCANNER) : scanner.scan_until(UNQUOTED_SCANNER)
while matched
Expand Down
5 changes: 4 additions & 1 deletion lib/active_record/connection_adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def disable_referential_integrity

def active?
return false unless @connection

raw_connection_do "SELECT 1"
true
rescue *connection_errors
Expand Down Expand Up @@ -253,6 +254,7 @@ def sqlserver_azure?

def database_prefix_remote_server?
return false if database_prefix.blank?

name = SQLServer::Utils.extract_identifiers(database_prefix)
name.fully_qualified? && name.object.blank?
end
Expand Down Expand Up @@ -420,7 +422,7 @@ def dblib_connect(config)
appname: config_appname(config),
login_timeout: config_login_timeout(config),
timeout: config_timeout(config),
encoding: config_encoding(config),
encoding: config_encoding(config),
azure: config[:azure],
contained: config[:contained]
).tap do |client|
Expand Down Expand Up @@ -476,6 +478,7 @@ def initialize_dateformatter

def version_year
return 2016 if sqlserver_version =~ /vNext/

/SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
rescue StandardError => e
2016
Expand Down
3 changes: 3 additions & 0 deletions lib/active_record/tasks/sqlserver_database_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def structure_dump(filename, extra_flags)
view_args = connection.views.map { |v| Shellwords.escape(v) }
command.concat(view_args)
raise "Error dumping database" unless Kernel.system(command.join(" "))

dump = File.read(filename)
dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
dump.gsub!(/^GO\n/, "") # Strip db GO statements
Expand Down Expand Up @@ -110,11 +111,13 @@ def local_database?(configuration)

def configuration_host_ip(configuration)
return nil unless configuration["host"]

Socket::getaddrinfo(configuration["host"], "echo", Socket::AF_INET)[0][3]
end

def local_ipaddr?(host_ip)
return false unless host_ip

LOCAL_IPADDR.any? { |ip| ip.include?(host_ip) }
end
end
Expand Down
10 changes: 7 additions & 3 deletions lib/arel/visitors/sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ def visit_Arel_Table o, collector
else
quote_table_name(o.name)
end
rescue Exception => e
quote_table_name(o.name)
rescue Exception => e
quote_table_name(o.name)
end
if o.table_alias
collector << "#{table_name} #{quote_table_name o.table_alias}"
Expand Down Expand Up @@ -176,6 +176,7 @@ def visit_Make_Fetch_Happen o, collector

def node_value(node)
return nil unless node

case node.expr
when NilClass then nil
when Numeric then node.expr
Expand All @@ -189,9 +190,11 @@ def select_statement_lock?

def make_Fetch_Possible_And_Deterministic o
return if o.limit.nil? && o.offset.nil?

t = table_From_Statement o
pk = primary_Key_From_Table t
return unless pk

if o.orders.empty?
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
o.orders = [pk.asc]
Expand Down Expand Up @@ -222,8 +225,9 @@ def table_From_Statement o

def primary_Key_From_Table t
return unless t

column_name = @connection.schema_cache.primary_keys(t.name) ||
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
@connection.schema_cache.columns_hash(t.name).first.try(:second).try(:name)
column_name ? t[column_name] : nil
end

Expand Down
Loading