Skip to content

Commit 83f6d57

Browse files
authored
Enable supports_json? for 2016 and up with a little bit of sugar. (#577)
Adds a simple JSON type for creating an underlying `nvarchar(max)` column to add JSON too. Since SQL Server does not have a true JSON type. This means supporting JSON from a Rails perspective is a simple as adding the proper SQL Server type to your model's column which has some form of underlying string type. ```ruby create_table :users do |t| t.string :name, :email t.json :data # Creates a nvarchar(max) column. end class Users < ActiveRecord::Base attribute :data, ActiveRecord::Type::SQLServer::Json.new end ``` This allows you to save and return JSON data and query it as needed. ```ruby User.create! name: 'DJ Ruby Rhod', data: nil User.create! name: 'Ken Collins', data: { 'admin' => true, 'foo' => 'bar' } admin = User.where("JSON_VALUE(data, '$.admin') = CAST(1 AS BIT)").first admin.data['foo'] # => "bar" ``` Fixes #485.
1 parent 01a7de4 commit 83f6d57

File tree

11 files changed

+83
-9
lines changed

11 files changed

+83
-9
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ create_table :in_memory_table, id: false,
2424
end
2525
```
2626

27+
* Enable supports_json? Fixes #577.
28+
29+
```ruby
30+
create_table :users do |t|
31+
t.string :name, :email
32+
t.json :data # Creates a nvarchar(max) column.
33+
end
34+
35+
class Users < ActiveRecord::Base
36+
attribute :data, ActiveRecord::Type::SQLServer::Json.new
37+
end
38+
39+
User.create! name: 'Ken Collins', data: { 'admin' => true, 'foo' => 'bar' }
40+
41+
admin = User.where("JSON_VALUE(data, '$.admin') = CAST(1 AS BIT)").first
42+
admin.data['foo'] # => "bar"
43+
```
44+
2745

2846
## v5.0.5
2947

RAILS5-TODO.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11

22
## Rails v5.0
33

4-
* Docs on Docker Usage & Testing - https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/547
5-
* Supports JSON - https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/485
6-
* Supports Comments - https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/486
7-
* Supports Indexed in Create - https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/483
8-
https://blog.dbi-services.com/sql-server-2014-tips-create-indexes-directly-via-create-table/
9-
Column Store https://msdn.microsoft.com/en-us/library/gg492153.aspx
10-
https://www.microsoft.com/en-us/sql-server/developer-get-started/node-mac
114
* Does the schema cache serialize properly since we conform to that now?
125
* Can we use `OPTIMIZE FOR UNKNOWN`
136
- http://sqlblog.com/blogs/aaron_bertrand/archive/2011/09/17/bad-habits-to-kick-using-exec-instead-of-sp-executesql.aspx

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ def initialize_native_database_types
281281
varbinary: { name: 'varbinary', limit: 8000 },
282282
binary: { name: 'varbinary(max)' },
283283
uuid: { name: 'uniqueidentifier' },
284-
ss_timestamp: { name: 'timestamp' }
284+
ss_timestamp: { name: 'timestamp' },
285+
json: { name: 'nvarchar(max)' }
285286
}
286287
end
287288

lib/active_record/connection_adapters/sqlserver/table_definition.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def ss_timestamp(*args, **options)
8585
args.each { |name| column(name, :ss_timestamp, options) }
8686
end
8787

88+
def json(*args, **options)
89+
args.each { |name| column(name, :text, options) }
90+
end
91+
8892
end
8993

9094
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition

lib/active_record/connection_adapters/sqlserver/type.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
# Other Data Types
4141
require 'active_record/connection_adapters/sqlserver/type/uuid'
4242
require 'active_record/connection_adapters/sqlserver/type/timestamp'
43+
require 'active_record/connection_adapters/sqlserver/type/json'
4344

4445
module ActiveRecord
4546
module Type
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module ActiveRecord
2+
module ConnectionAdapters
3+
module SQLServer
4+
module Type
5+
class Json < ActiveRecord::Type::Internal::AbstractJson
6+
7+
end
8+
end
9+
end
10+
end
11+
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def supports_datetime_with_precision?
130130
end
131131

132132
def supports_json?
133-
true
133+
@version_year >= 2016
134134
end
135135

136136
def supports_comments?
@@ -304,6 +304,8 @@ def initialize_type_map(m)
304304
register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
305305
m.alias_type 'string', 'nvarchar(4000)'
306306
m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
307+
m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
308+
m.alias_type 'json', 'nvarchar(max)'
307309
m.register_type 'ntext', SQLServer::Type::UnicodeText.new
308310
# Binary Strings
309311
register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary

test/cases/json_test_sqlserver.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'cases/helper_sqlserver'
2+
3+
if ActiveRecord::Base.connection.supports_json?
4+
class JsonTestSQLServer < ActiveRecord::TestCase
5+
6+
before do
7+
@o1 = SSTestDatatypeMigrationJson.create! json_col: { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
8+
@o2 = SSTestDatatypeMigrationJson.create! json_col: { 'a' => nil, 'b' => 'b', 'c' => 'c' }
9+
@o3 = SSTestDatatypeMigrationJson.create! json_col: { 'x' => 1, 'y' => 2, 'z' => 3 }
10+
@o4 = SSTestDatatypeMigrationJson.create! json_col: { 'array' => [1, 2, 3] }
11+
@o5 = SSTestDatatypeMigrationJson.create! json_col: nil
12+
end
13+
14+
it 'can return and save JSON data' do
15+
SSTestDatatypeMigrationJson.find(@o1.id).json_col.must_equal({ 'a' => 'a', 'b' => 'b', 'c' => 'c' })
16+
@o1.json_col = { 'a' => 'a' }
17+
@o1.json_col.must_equal({ 'a' => 'a' })
18+
@o1.save!
19+
@o1.reload.json_col.must_equal({ 'a' => 'a' })
20+
end
21+
22+
it 'can use ISJSON function' do
23+
SSTestDatatypeMigrationJson.where('ISJSON(json_col) > 0').count.must_equal 4
24+
SSTestDatatypeMigrationJson.where('ISJSON(json_col) IS NULL').count.must_equal 1
25+
end
26+
27+
it 'can use JSON_VALUE function' do
28+
SSTestDatatypeMigrationJson.where("JSON_VALUE(json_col, '$.b') = 'b'").count.must_equal 2
29+
end
30+
31+
end
32+
end

test/cases/schema_dumper_test_sqlserver.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
9797
columns['varbinary_col'].sql_type.must_equal 'varbinary(8000)'
9898
columns['uuid_col'].sql_type.must_equal 'uniqueidentifier'
9999
columns['sstimestamp_col'].sql_type.must_equal 'timestamp'
100+
columns['json_col'].sql_type.must_equal 'nvarchar(max)'
100101
assert_line :real_col, type: 'real', limit: nil, precision: nil, scale: nil, default: nil
101102
assert_line :money_col, type: 'money', limit: nil, precision: 19, scale: 4, default: nil
102103
assert_line :datetime2_col, type: 'datetime', limit: nil, precision: 7, scale: nil, default: nil
@@ -111,6 +112,7 @@ class SchemaDumperTestSQLServer < ActiveRecord::TestCase
111112
assert_line :varbinary_col, type: 'varbinary', limit: nil, precision: nil, scale: nil, default: nil
112113
assert_line :uuid_col, type: 'uuid', limit: nil, precision: nil, scale: nil, default: nil
113114
assert_line :sstimestamp_col, type: 'ss_timestamp', limit: nil, precision: nil, scale: nil, default: nil
115+
assert_line :json_col, type: 'text', limit: 2147483647, precision: nil, scale: nil, default: nil
114116
end
115117

116118
# Special Cases
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
class SSTestDatatypeMigration < ActiveRecord::Base
22
self.table_name = :sst_datatypes_migration
33
end
4+
5+
class SSTestDatatypeMigrationJson < ActiveRecord::Base
6+
self.table_name = :sst_datatypes_migration
7+
attribute :json_col, ActiveRecord::Type::SQLServer::Json.new
8+
end

0 commit comments

Comments
 (0)