diff --git a/Dockerfile b/Dockerfile index 9a277ddb..80ace586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,18 @@ FROM ruby:2.3.1-slim -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev libmysqlclient-dev nodejs nodejs-legacy npm git +RUN apt-get update -qq && \ + apt-get install -y \ + build-essential libpq-dev libmysqlclient-dev nodejs nodejs-legacy npm git \ + freetds-dev freetds-bin unixodbc unixodbc-dev tdsodbc libc6-dev wget + +RUN mkdir /tmp/freetds && \ + cd /tmp/freetds && \ + wget ftp://ftp.freetds.org/pub/freetds/stable/freetds-1.00.21.tar.gz && \ + tar -xzf freetds-1.00.21.tar.gz && \ + cd freetds-1.00.21 && \ + ./configure --prefix=/usr/local --with-tdsver=7.3 && \ + make && \ + make install + RUN mkdir /app ADD Gemfile /tmp/Gemfile diff --git a/Gemfile b/Gemfile index 63417826..77cea2dc 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gem 'rails', '~> 4.2.6' gem 'pg', '~> 0.15' gem 'mysql2' gem 'activerecord4-redshift-adapter', '~> 0.2.0' +gem 'activerecord-sqlserver-adapter', '~> 4.2.0' +gem 'tiny_tds' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index ce768b2a..66369ac3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -32,6 +32,8 @@ GEM activemodel (= 4.2.10) activesupport (= 4.2.10) arel (~> 6.0) + activerecord-sqlserver-adapter (4.2.18) + activerecord (~> 4.2.1) activerecord4-redshift-adapter (0.2.1) activerecord (~> 4.2.0) pg @@ -46,7 +48,7 @@ GEM sshkit (>= 1.6.1, != 1.7.0) arel (6.0.4) ast (2.3.0) - brakeman (4.0.1) + brakeman (4.1.0) builder (3.2.3) byebug (9.1.0) callsite (0.0.11) @@ -183,10 +185,10 @@ GEM multi_json (~> 1.3) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.3.1) - omniauth-oauth2 (1.4.0) - oauth2 (~> 1.0) + omniauth-oauth2 (1.5.0) + oauth2 (~> 1.1) omniauth (~> 1.2) - parallel (1.12.0) + parallel (1.12.1) parser (2.4.0.2) ast (~> 2.3) pg (0.21.0) @@ -236,8 +238,7 @@ GEM activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake + rainbow (3.0.0) rake (12.3.0) rb-fsevent (0.10.2) rb-inotify (0.9.10) @@ -266,18 +267,18 @@ GEM rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) rspec-support (3.7.0) - rubocop (0.51.0) + rubocop (0.52.0) parallel (~> 1.10) - parser (>= 2.3.3.1, < 3.0) + parser (>= 2.4.0.2, < 3.0) powerpack (~> 0.1) - rainbow (>= 2.2.2, < 3.0) + rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-prof (0.16.2) + ruby-prof (0.17.0) ruby-progressbar (1.9.0) ruby_parser (3.10.1) sexp_processor (~> 4.9) - sass (3.5.3) + sass (3.5.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -311,6 +312,7 @@ GEM thread_safe (0.3.6) tilt (2.0.8) tins (1.16.3) + tiny_tds (2.1.0) tzinfo (1.2.4) thread_safe (~> 0.1) uglifier (4.0.2) @@ -325,6 +327,7 @@ PLATFORMS DEPENDENCIES action_args active_decorator + activerecord-sqlserver-adapter (~> 4.2.0) activerecord4-redshift-adapter (~> 0.2.0) addressable brakeman @@ -365,6 +368,7 @@ DEPENDENCIES ruby-prof sass-rails (~> 5.0) simplecov + tiny_tds uglifier (>= 1.3.0) BUNDLED WITH diff --git a/app/models/data_source.rb b/app/models/data_source.rb index d7993437..e0cb950f 100644 --- a/app/models/data_source.rb +++ b/app/models/data_source.rb @@ -75,6 +75,14 @@ def source_table_names ) tables ORDER BY schemaname, tablename; SQL + when ActiveRecord::ConnectionAdapters::SQLServerAdapter + schemas_and_tables = source_base_class.connection.select_rows(<<-SQL, 'SCHEMA') + SELECT table_schema + , table_name + FROM information_schema.tables + WHERE table_type = 'BASE TABLE'; + SQL + schemas_and_tables.map {|table_schema, table_name| [table_schema, table_name]} else source_base_class.connection.tables.map {|table_name| [dbname, table_name] } end diff --git a/app/models/data_source_table.rb b/app/models/data_source_table.rb index 392f196e..ea976fbb 100644 --- a/app/models/data_source_table.rb +++ b/app/models/data_source_table.rb @@ -16,10 +16,18 @@ def initialize(data_source, schema_name, table_name) def fetch_rows(limit=20) data_source.access_logging do adapter = connection.pool.connections.first - column_names = columns.map {|column| adapter.quote_column_name(column.name) }.join(", ") - rows = connection.select_rows(<<-SQL, "#{table_name.classify} Load") - SELECT #{column_names} FROM #{adapter.quote_table_name(full_table_name)} LIMIT #{limit}; - SQL + decode_with = data_source.encoding or 'UTF-8' + encode_to = Encoding.default_internal or 'UTF-8' + column_names = columns.map {|column| adapter.quote_column_name(column.name.encode(encode_to, decode_with)) }.join(", ") + if data_source[:adapter] == 'sqlserver' + rows = connection.select_rows(<<-SQL, "#{table_name.classify} Load") + SELECT TOP #{limit} #{column_names} FROM #{adapter.quote_table_name(full_table_name)}; + SQL + else + rows = connection.select_rows(<<-SQL, "#{table_name.classify} Load") + SELECT #{column_names} FROM #{adapter.quote_table_name(full_table_name)} LIMIT #{limit}; + SQL + end rows.map {|row| columns.zip(row).map {|column, value| column.type_cast_from_database(value) } } diff --git a/app/models/table_memo_raw_dataset_column.rb b/app/models/table_memo_raw_dataset_column.rb index f82e6b2e..a7639469 100644 --- a/app/models/table_memo_raw_dataset_column.rb +++ b/app/models/table_memo_raw_dataset_column.rb @@ -2,12 +2,24 @@ class TableMemoRawDatasetColumn < ActiveRecord::Base belongs_to :table_memo_raw_dataset, class_name: "TableMemoRawDataset" def format_value(value) - case sql_type - when 'timestamp without time zone' - d, t, z = value.to_s.split - "#{d} #{t}" + datasource_encoding = table_memo_raw_dataset.table_memo.database_memo.data_source.encoding + datasource_adapter = table_memo_raw_dataset.table_memo.database_memo.data_source.adapter + if datasource_adapter == 'sqlserver' + case sql_type + when 'timestamp' + '0x' + value.unpack('H*')[0] + else + dst_enc = Encoding.default_internal or 'UTF-8' + value.to_s.encode(dst_enc, datasource_encoding).gsub(/\u0000/, '') + end else - value.to_s + case sql_type + when 'timestamp without time zone' + d, t, z = value.to_s.split + "#{d} #{t}" + else + value.to_s + end end end end diff --git a/app/views/data_sources/_form.html.haml b/app/views/data_sources/_form.html.haml index aeedc2c0..c169a497 100644 --- a/app/views/data_sources/_form.html.haml +++ b/app/views/data_sources/_form.html.haml @@ -8,7 +8,7 @@ .col-sm-10= f.text_field :description, class: "form-control", placeholder: "localhost database" .form-group = f.label :adapter, class: "col-sm-2 control-label" - .col-sm-10= f.select :adapter, %w(redshift postgresql mysql2), {}, class: "form-control" + .col-sm-10= f.select :adapter, %w(redshift postgresql mysql2 sqlserver), {}, class: "form-control" .form-group = f.label :host, class: "col-sm-2 control-label" .col-sm-10= f.text_field :host, class: "form-control", placeholder: "localhost" diff --git a/config/database.yml b/config/database.yml index e57e352c..11f0fc74 100644 --- a/config/database.yml +++ b/config/database.yml @@ -13,4 +13,5 @@ test: database: dmemo_test production: + <<: *default url: <%= ENV["DATABASE_URL"] %>